kit

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

jit_runner.c (24474B)


      1 /* jit_runner — Path J harness driver.
      2  *
      3  * Usage:
      4  *   jit_runner [--gc-sections] [--use-resolver]
      5  *              [--check-absent SYM] [--check-present SYM]
      6  *              [--linker-script <path>]
      7  *              [--archive [--whole-archive] <lib.a>] <in.o> ...
      8  *
      9  * Reads .o (and optionally .a) inputs, calls kit_link_jit (which runs
     10  * init_array ctors), calls test_main(), calls kit_jit_run_dtors(), then
     11  * calls test_post_fini() if it exists. Exits with the combined result.
     12  *
     13  * --use-resolver: enables a default extern resolver that returns the
     14  *   address of a static int == 42 for any unresolved symbol. Used for
     15  *   the extern_resolver test case (28).
     16  *
     17  * Sanity: after every successful JIT load, verifies that
     18  * kit_jit_lookup(jit, "__kit_test_sentinel__") returns NULL (tests
     19  * the lookup-miss path, covering case 29).
     20  *
     21  * Compiled against libkit.a with -I$(ROOT)/include. */
     22 
     23 #ifndef _GNU_SOURCE
     24 #define _GNU_SOURCE
     25 #endif
     26 
     27 #include <fcntl.h>
     28 #include <kit/core.h>
     29 #include <kit/jit.h>
     30 #include <kit/link.h>
     31 #include <kit/wasm.h>
     32 #include <stdarg.h>
     33 #include <stdint.h>
     34 #include <stdio.h>
     35 #include <stdlib.h>
     36 #include <string.h>
     37 #include <sys/mman.h>
     38 #include <sys/stat.h>
     39 #include <unistd.h>
     40 
     41 #include "lib/kit_test_target.h"
     42 
     43 static void* h_alloc(KitHeap* h, size_t n, size_t a) {
     44   (void)h;
     45   (void)a;
     46   return malloc(n);
     47 }
     48 static void* h_realloc(KitHeap* h, void* p, size_t o, size_t n, size_t a) {
     49   (void)h;
     50   (void)o;
     51   (void)a;
     52   return realloc(p, n);
     53 }
     54 static void h_free(KitHeap* h, void* p, size_t n) {
     55   (void)h;
     56   (void)n;
     57   free(p);
     58 }
     59 static KitHeap g_heap = {h_alloc, h_realloc, h_free, NULL};
     60 
     61 /* Canned host import for wasm test fixtures (e.g. host_import_add.wat).
     62  * Signature matches the kit-instance ABI: explicit instance + wasm
     63  * params. */
     64 int32_t test_host_add(KitWasmInstance* inst, int32_t a, int32_t b) {
     65   (void)inst;
     66   return a + b;
     67 }
     68 
     69 static void diag_fn(KitDiagSink* s, KitDiagKind k, KitSrcLoc loc,
     70                     const char* fmt, va_list ap) {
     71   static const char* names[] = {"note", "warning", "error", "fatal"};
     72   (void)s;
     73   (void)loc;
     74   fprintf(stderr, "%s: ", names[k]);
     75   vfprintf(stderr, fmt, ap);
     76   fputc('\n', stderr);
     77 }
     78 static KitDiagSink g_diag = {diag_fn, NULL, 0, 0};
     79 
     80 static void free_compiler_target(KitCompiler* compiler, KitTarget* target) {
     81   kit_compiler_free(compiler);
     82   kit_target_free(target);
     83 }
     84 
     85 /* Mirrors driver/env.c — see that file for the strict-W^X dual-mapping
     86  * rationale. */
     87 #if defined(__APPLE__)
     88 #include <mach/mach.h>
     89 #include <mach/mach_vm.h>
     90 #define XM_DUAL_APPLE 1
     91 #else
     92 #define XM_DUAL_APPLE 0
     93 #endif
     94 #if defined(__linux__)
     95 #include <sys/syscall.h>
     96 #define XM_DUAL_LINUX 1
     97 #else
     98 #define XM_DUAL_LINUX 0
     99 #endif
    100 static int xm_to_posix(int p) {
    101   int q = 0;
    102   if (p & KIT_PROT_READ) q |= PROT_READ;
    103   if (p & KIT_PROT_WRITE) q |= PROT_WRITE;
    104   if (p & KIT_PROT_EXEC) q |= PROT_EXEC;
    105   return q;
    106 }
    107 #if XM_DUAL_LINUX && defined(__x86_64__) && defined(MAP_32BIT)
    108 #define XM_MAP_32BIT MAP_32BIT
    109 static uintptr_t g_xm_low_runtime_hint = 0x40000000u;
    110 static void* xm_low_runtime_hint(size_t n) {
    111   uintptr_t p = g_xm_low_runtime_hint;
    112   uintptr_t step = (uintptr_t)((n + 0xffffu) & ~(size_t)0xffffu);
    113   if (step < 0x10000u) step = 0x10000u;
    114   g_xm_low_runtime_hint = p + step + 0x10000u;
    115   if (g_xm_low_runtime_hint > 0x78000000u) g_xm_low_runtime_hint = 0x40000000u;
    116   return (void*)p;
    117 }
    118 #elif XM_DUAL_LINUX
    119 #define XM_MAP_32BIT 0
    120 static void* xm_low_runtime_hint(size_t n) {
    121   (void)n;
    122   return NULL;
    123 }
    124 #endif
    125 typedef struct XmTok {
    126   void* w;
    127   void* r;
    128   size_t n;
    129 } XmTok;
    130 static KitStatus xm_reserve_single(size_t n, KitExecMemRegion* out) {
    131   void* p =
    132       mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
    133   if (p == MAP_FAILED) return KIT_NOMEM;
    134   out->write = out->runtime = p;
    135   out->size = n;
    136   out->token = NULL;
    137   return KIT_OK;
    138 }
    139 static KitStatus xm_reserve(void* u, size_t n, int p, KitExecMemRegion* out) {
    140   (void)u;
    141   if (!out || !n) return KIT_INVALID;
    142   if (!(p & KIT_PROT_EXEC)) return xm_reserve_single(n, out);
    143 #if XM_DUAL_APPLE
    144   {
    145     void* w =
    146         mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
    147     mach_vm_address_t r = 0;
    148     vm_prot_t cur = 0, max = 0;
    149     XmTok* tok;
    150     if (w == MAP_FAILED) return KIT_NOMEM;
    151     if (mach_vm_remap(mach_task_self(), &r, (mach_vm_size_t)n, 0,
    152                       VM_FLAGS_ANYWHERE, mach_task_self(),
    153                       (mach_vm_address_t)(uintptr_t)w, FALSE, &cur, &max,
    154                       VM_INHERIT_NONE) != KERN_SUCCESS) {
    155       munmap(w, n);
    156       return KIT_NOMEM;
    157     }
    158     if (mprotect((void*)(uintptr_t)r, n, PROT_READ) != 0) {
    159       munmap((void*)(uintptr_t)r, n);
    160       munmap(w, n);
    161       return KIT_NOMEM;
    162     }
    163     tok = (XmTok*)malloc(sizeof(*tok));
    164     if (!tok) {
    165       munmap((void*)(uintptr_t)r, n);
    166       munmap(w, n);
    167       return KIT_NOMEM;
    168     }
    169     tok->w = w;
    170     tok->r = (void*)(uintptr_t)r;
    171     tok->n = n;
    172     out->write = w;
    173     out->runtime = (void*)(uintptr_t)r;
    174     out->size = n;
    175     out->token = tok;
    176     return KIT_OK;
    177   }
    178 #elif XM_DUAL_LINUX
    179   {
    180     int fd = (int)syscall(SYS_memfd_create, "kit-jit-test", 0u);
    181     void *w, *r;
    182     XmTok* tok;
    183     if (fd < 0) return KIT_NOMEM;
    184     if (ftruncate(fd, (off_t)n) != 0) {
    185       close(fd);
    186       return KIT_NOMEM;
    187     }
    188     w = mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_SHARED | XM_MAP_32BIT, fd, 0);
    189     if (w == MAP_FAILED) {
    190       close(fd);
    191       return KIT_NOMEM;
    192     }
    193     r = mmap(xm_low_runtime_hint(n), n, PROT_READ, MAP_SHARED | XM_MAP_32BIT,
    194              fd, 0);
    195     close(fd);
    196     if (r == MAP_FAILED) {
    197       munmap(w, n);
    198       return KIT_NOMEM;
    199     }
    200     tok = (XmTok*)malloc(sizeof(*tok));
    201     if (!tok) {
    202       munmap(r, n);
    203       munmap(w, n);
    204       return KIT_NOMEM;
    205     }
    206     tok->w = w;
    207     tok->r = r;
    208     tok->n = n;
    209     out->write = w;
    210     out->runtime = r;
    211     out->size = n;
    212     out->token = tok;
    213     return KIT_OK;
    214   }
    215 #else
    216   return xm_reserve_single(n, out);
    217 #endif
    218 }
    219 static KitStatus xm_protect(void* u, void* a, size_t n, int p) {
    220   (void)u;
    221   return mprotect(a, n, xm_to_posix(p)) == 0 ? KIT_OK : KIT_IO;
    222 }
    223 static void xm_release(void* u, KitExecMemRegion* region) {
    224   (void)u;
    225   if (!region || !region->size) return;
    226   if (region->token) {
    227     XmTok* tok = (XmTok*)region->token;
    228     if (tok->r && tok->r != tok->w) munmap(tok->r, tok->n);
    229     if (tok->w) munmap(tok->w, tok->n);
    230     free(tok);
    231   } else if (region->write) {
    232     munmap(region->write, region->size);
    233   }
    234   region->write = region->runtime = NULL;
    235   region->size = 0;
    236   region->token = NULL;
    237 }
    238 static void xm_flush(void* u, void* a, size_t n) {
    239   (void)u;
    240 #if defined(__aarch64__) || defined(__arm__) || defined(__riscv)
    241 #if defined(__riscv)
    242   __asm__ __volatile__("fence.i" ::: "memory");
    243 #endif
    244   __builtin___clear_cache((char*)a, (char*)a + n);
    245 #else
    246   (void)a;
    247   (void)n;
    248 #endif
    249 }
    250 static KitExecMem g_execmem = {
    251     16 * 1024, xm_reserve, xm_protect, xm_release, xm_flush, NULL,
    252 };
    253 
    254 static int slurp(const char* path, uint8_t** out, size_t* len) {
    255   int fd = open(path, O_RDONLY);
    256   if (fd < 0) return -1;
    257   struct stat sb;
    258   if (fstat(fd, &sb) < 0) {
    259     close(fd);
    260     return -1;
    261   }
    262   size_t n = (size_t)sb.st_size;
    263   uint8_t* buf = malloc(n ? n : 1);
    264   if (!buf) {
    265     close(fd);
    266     return -1;
    267   }
    268   size_t got = 0;
    269   while (got < n) {
    270     ssize_t k = read(fd, buf + got, n - got);
    271     if (k <= 0) {
    272       free(buf);
    273       close(fd);
    274       return -1;
    275     }
    276     got += (size_t)k;
    277   }
    278   close(fd);
    279   *out = buf;
    280   *len = n;
    281   return 0;
    282 }
    283 
    284 /* Default extern resolver: returns address of a static int == 42.
    285  * Used by case 28 (extern_resolver); harmless for any case without
    286  * unresolved symbols since the resolver is never invoked. */
    287 static int g_extern_default_value = 42;
    288 static void* low_extern_default_value(void) {
    289 #if defined(__linux__) && defined(__x86_64__) && defined(MAP_32BIT)
    290   static int* p;
    291   if (!p) {
    292     void* mem = mmap((void*)0x30000000u, 4096, PROT_READ | PROT_WRITE,
    293                      MAP_PRIVATE | MAP_ANON | MAP_32BIT, -1, 0);
    294     if (mem != MAP_FAILED) {
    295       p = (int*)mem;
    296       *p = 42;
    297     }
    298   }
    299   return p ? (void*)p : (void*)&g_extern_default_value;
    300 #else
    301   return &g_extern_default_value;
    302 #endif
    303 }
    304 static void* extern_resolver(void* user, KitSlice name) {
    305   (void)user;
    306   (void)name;
    307   return low_extern_default_value();
    308 }
    309 
    310 /* The JIT resolves thread-local accesses to in-image storage (single-threaded:
    311  * the in-image .tdata/.tbss is the single instance — see the TLS relaxation in
    312  * src/link/link_jit.c). The host still seeds the thread pointer below so the
    313  * freestanding ELF startup convention is honored on hosts that run the image
    314  * natively, but no KitJitTls vtable / per-thread block is needed. */
    315 #if defined(__aarch64__) || defined(__arm64__)
    316 __attribute__((noinline, no_sanitize("address", "undefined"))) static int
    317 call_with_aarch64_tls(int (*fn)(void), void* tls_block) {
    318   void* old_tp;
    319   int result;
    320   __asm__ volatile("mrs %0, tpidr_el0" : "=r"(old_tp)::"memory");
    321   __asm__ volatile("msr tpidr_el0, %0" ::"r"(tls_block) : "memory");
    322   result = fn();
    323   __asm__ volatile("msr tpidr_el0, %0" ::"r"(old_tp) : "memory");
    324   return result;
    325 }
    326 #endif
    327 
    328 #if defined(__x86_64__) && defined(__linux__)
    329 static long x64_arch_prctl_raw(long code, unsigned long addr) {
    330   register long rax __asm__("rax") = 158; /* SYS_arch_prctl */
    331   register long rdi __asm__("rdi") = code;
    332   register long rsi __asm__("rsi") = (long)addr;
    333   __asm__ volatile("syscall"
    334                    : "+r"(rax)
    335                    : "r"(rdi), "r"(rsi)
    336                    : "rcx", "r11", "memory");
    337   return rax;
    338 }
    339 #endif
    340 
    341 typedef struct WasmRunnerMemoryPrefix {
    342   uint8_t* data;
    343   uint64_t pages;
    344   uint64_t max_pages;
    345   uint32_t flags;
    346 } WasmRunnerMemoryPrefix;
    347 
    348 typedef struct WasmRunnerInstanceAlloc {
    349   uint8_t* instance;
    350   uint8_t** memories;
    351   uint32_t nmemories;
    352 } WasmRunnerInstanceAlloc;
    353 
    354 typedef void (*WasmRunnerInitFn)(void*);
    355 typedef int (*WasmRunnerMainFn)(void*);
    356 
    357 #define WASM_RUNNER_MAX_INSTANCE_BYTES (64ull * 1024ull * 1024ull)
    358 #define WASM_RUNNER_MAX_TOTAL_MEMORY_BYTES (1024ull * 1024ull * 1024ull)
    359 
    360 static int wasm_runner_u64_to_size(uint64_t n, size_t* out, const char* what) {
    361   if (n > (uint64_t)SIZE_MAX) {
    362     fprintf(stderr, "jit-runner: wasm %s exceeds host size_t\n", what);
    363     return 1;
    364   }
    365   *out = (size_t)n;
    366   return 0;
    367 }
    368 
    369 static int wasm_runner_page_bytes(uint64_t pages, uint64_t* out) {
    370   if (pages > UINT64_MAX / (uint64_t)KIT_WASM_PAGE_SIZE) {
    371     fprintf(stderr, "jit-runner: wasm memory size overflows\n");
    372     return 1;
    373   }
    374   *out = pages * (uint64_t)KIT_WASM_PAGE_SIZE;
    375   return 0;
    376 }
    377 
    378 static void wasm_runner_free_instance(WasmRunnerInstanceAlloc* alloc) {
    379   if (!alloc) return;
    380   if (alloc->memories) {
    381     for (uint32_t i = 0; i < alloc->nmemories; ++i) free(alloc->memories[i]);
    382     free(alloc->memories);
    383   }
    384   free(alloc->instance);
    385   memset(alloc, 0, sizeof *alloc);
    386 }
    387 
    388 static int wasm_runner_make_instance(KitJit* jit,
    389                                      WasmRunnerInstanceAlloc* out) {
    390   KitWasmRuntimeLayout layout;
    391   WasmRunnerInstanceAlloc alloc;
    392   uint64_t instance_bytes;
    393   uint64_t total_memory_bytes = 0;
    394   size_t instance_size;
    395   KitStatus st;
    396   memset(&alloc, 0, sizeof alloc);
    397   if (!out) return 1;
    398   memset(out, 0, sizeof *out);
    399   st = kit_wasm_get_runtime_layout(jit, &layout);
    400   if (st != KIT_OK) {
    401     fprintf(stderr, "jit-runner: wasm runtime layout metadata %s\n",
    402             st == KIT_NOT_FOUND ? "missing" : "malformed");
    403     return 1;
    404   }
    405   instance_bytes = layout.instance_size ? layout.instance_size : 1u;
    406   if (instance_bytes > WASM_RUNNER_MAX_INSTANCE_BYTES) {
    407     fprintf(stderr, "jit-runner: wasm instance too large: %llu bytes\n",
    408             (unsigned long long)instance_bytes);
    409     return 1;
    410   }
    411   if (wasm_runner_u64_to_size(instance_bytes, &instance_size, "instance") != 0)
    412     return 1;
    413   alloc.instance = (uint8_t*)calloc(1, instance_size);
    414   alloc.nmemories = layout.nmemories;
    415   if (!alloc.instance) {
    416     fprintf(stderr, "jit-runner: out of memory\n");
    417     return 1;
    418   }
    419   if (layout.nmemories) {
    420     if ((uint64_t)layout.nmemories > (uint64_t)SIZE_MAX / sizeof(uint8_t*)) {
    421       fprintf(stderr, "jit-runner: wasm memory count too large\n");
    422       wasm_runner_free_instance(&alloc);
    423       return 1;
    424     }
    425     alloc.memories = (uint8_t**)calloc(layout.nmemories, sizeof(uint8_t*));
    426     if (!alloc.memories) {
    427       fprintf(stderr, "jit-runner: out of memory\n");
    428       wasm_runner_free_instance(&alloc);
    429       return 1;
    430     }
    431   }
    432   for (uint32_t i = 0; i < layout.nmemories; ++i) {
    433     const KitWasmMemoryLayout* ml = &layout.memories[i];
    434     uint64_t mem_bytes;
    435     size_t mem_size;
    436     WasmRunnerMemoryPrefix* rec;
    437     if (ml->max_pages < ml->min_pages) {
    438       fprintf(stderr, "jit-runner: wasm memory maximum below minimum\n");
    439       wasm_runner_free_instance(&alloc);
    440       return 1;
    441     }
    442     if (ml->offset > instance_bytes ||
    443         instance_bytes - ml->offset < sizeof(WasmRunnerMemoryPrefix)) {
    444       fprintf(stderr, "jit-runner: wasm memory record outside instance\n");
    445       wasm_runner_free_instance(&alloc);
    446       return 1;
    447     }
    448     if (wasm_runner_page_bytes(ml->max_pages, &mem_bytes) != 0) {
    449       wasm_runner_free_instance(&alloc);
    450       return 1;
    451     }
    452     if (mem_bytes > WASM_RUNNER_MAX_TOTAL_MEMORY_BYTES ||
    453         total_memory_bytes > WASM_RUNNER_MAX_TOTAL_MEMORY_BYTES - mem_bytes) {
    454       fprintf(stderr,
    455               "jit-runner: wasm linear memory reservation exceeds %llu bytes\n",
    456               (unsigned long long)WASM_RUNNER_MAX_TOTAL_MEMORY_BYTES);
    457       wasm_runner_free_instance(&alloc);
    458       return 1;
    459     }
    460     total_memory_bytes += mem_bytes;
    461     if (wasm_runner_u64_to_size(mem_bytes, &mem_size, "linear memory") != 0) {
    462       wasm_runner_free_instance(&alloc);
    463       return 1;
    464     }
    465     if (mem_size) {
    466       alloc.memories[i] = (uint8_t*)calloc(1, mem_size);
    467       if (!alloc.memories[i]) {
    468         fprintf(stderr, "jit-runner: out of memory\n");
    469         wasm_runner_free_instance(&alloc);
    470         return 1;
    471       }
    472     }
    473     rec = (WasmRunnerMemoryPrefix*)(alloc.instance + ml->offset);
    474     rec->data = alloc.memories[i];
    475   }
    476   *out = alloc;
    477   return 0;
    478 }
    479 
    480 static int wasm_runner_host_add_type(const KitWasmImportType* type) {
    481   return type && type->nparams == 2u && type->nresults == 1u &&
    482          type->params[0] == KIT_WASM_VAL_I32 &&
    483          type->params[1] == KIT_WASM_VAL_I32 &&
    484          type->results[0] == KIT_WASM_VAL_I32;
    485 }
    486 
    487 static void* wasm_runner_resolve_import(void* user, const char* module,
    488                                         const char* field,
    489                                         const KitWasmImportType* type) {
    490   (void)user;
    491   if (!module || !field) return NULL;
    492   if (strcmp(module, "env") == 0 && strcmp(field, "host_add") == 0 &&
    493       wasm_runner_host_add_type(type))
    494     return (void*)(uintptr_t)test_host_add;
    495   return NULL;
    496 }
    497 
    498 int main(int argc, char** argv) {
    499   {
    500     long ps = sysconf(_SC_PAGESIZE);
    501     if (ps > 0) g_execmem.page_size = (size_t)ps;
    502   }
    503   int gc_sections = 0;
    504   int use_resolver = 0;
    505   int next_archive = 0;
    506   int next_whole = 0;
    507   /* --check-absent SYM:  after link, verify symbol is not in image.
    508    * --check-present SYM: after link, verify symbol IS in image. */
    509   const char* check_absent = NULL;
    510   const char* check_present = NULL;
    511   const char* entry_sym = "test_main";
    512   const char* script_path = NULL;
    513 
    514   KitSlice objs[64];
    515   const char* obj_names[64];
    516   KitLinkArchiveInput archives[16];
    517   uint32_t nobj = 0, narc = 0;
    518   uint8_t* bufs[80];
    519   int nbufs = 0;
    520 
    521   for (int i = 1; i < argc; i++) {
    522     if (!strcmp(argv[i], "--gc-sections")) {
    523       gc_sections = 1;
    524     } else if (!strcmp(argv[i], "--use-resolver")) {
    525       use_resolver = 1;
    526     } else if (!strcmp(argv[i], "--archive")) {
    527       next_archive = 1;
    528     } else if (!strcmp(argv[i], "--whole-archive")) {
    529       next_whole = 1;
    530       next_archive = 1;
    531     } else if (!strcmp(argv[i], "--check-absent") && i + 1 < argc) {
    532       check_absent = argv[++i];
    533     } else if (!strcmp(argv[i], "--check-present") && i + 1 < argc) {
    534       check_present = argv[++i];
    535     } else if (!strcmp(argv[i], "--linker-script") && i + 1 < argc) {
    536       script_path = argv[++i];
    537     } else if (!strcmp(argv[i], "--entry") && i + 1 < argc) {
    538       entry_sym = argv[++i];
    539     } else {
    540       uint8_t* data;
    541       size_t len;
    542       if (slurp(argv[i], &data, &len)) {
    543         fprintf(stderr, "jit-runner: cannot read %s\n", argv[i]);
    544         return 2;
    545       }
    546       bufs[nbufs++] = data;
    547       if (next_archive) {
    548         KitLinkArchiveInput* a = &archives[narc++];
    549         memset(a, 0, sizeof(*a));
    550         a->name = kit_slice_cstr(argv[i]);
    551         a->bytes.data = data;
    552         a->bytes.len = len;
    553         a->whole_archive = (uint8_t)next_whole;
    554         next_archive = 0;
    555         next_whole = 0;
    556       } else {
    557         obj_names[nobj] = argv[i];
    558         KitSlice* o = &objs[nobj++];
    559         memset(o, 0, sizeof(*o));
    560         o->data = data;
    561         o->len = len;
    562       }
    563     }
    564   }
    565 
    566   KitTargetSpec target;
    567   if (kit_test_target_init(&target) != 0) {
    568     fprintf(stderr, "jit_runner: kit_test_target_init failed\n");
    569     return 2;
    570   }
    571 
    572   KitContext ctx;
    573   memset(&ctx, 0, sizeof(ctx));
    574   ctx.heap = &g_heap;
    575   ctx.diag = &g_diag;
    576   ctx.now = -1;
    577 
    578   KitTargetOptions target_opts;
    579   memset(&target_opts, 0, sizeof target_opts);
    580   target_opts.spec = target;
    581   KitTarget* kt = NULL;
    582   if (kit_target_new(&ctx, &target_opts, &kt) != KIT_OK || !kt) {
    583     fprintf(stderr, "jit-runner: target_new failed\n");
    584     return 2;
    585   }
    586   KitCompiler* c = NULL;
    587   if (kit_compiler_new(kt, &ctx, &c) != KIT_OK || !c) {
    588     fprintf(stderr, "jit-runner: compiler_new failed\n");
    589     kit_target_free(kt);
    590     return 2;
    591   }
    592 
    593   KitJitHost jhost;
    594   memset(&jhost, 0, sizeof(jhost));
    595   jhost.execmem = &g_execmem;
    596 
    597   KitLinkSessionOptions opts;
    598   KitLinkSession* link = NULL;
    599   KitLinkScript* script = NULL;
    600   memset(&opts, 0, sizeof(opts));
    601   opts.output_kind = KIT_LINK_OUTPUT_JIT;
    602   opts.entry = kit_slice_cstr(entry_sym);
    603   opts.gc_sections = gc_sections;
    604   opts.jit_host = &jhost;
    605   if (use_resolver) {
    606     opts.extern_resolver = extern_resolver;
    607     opts.extern_resolver_user = NULL;
    608   }
    609 
    610   if (script_path) {
    611     uint8_t* sbytes;
    612     size_t slen;
    613     if (slurp(script_path, &sbytes, &slen)) {
    614       fprintf(stderr, "jit-runner: cannot read %s\n", script_path);
    615       free_compiler_target(c, kt);
    616       return 2;
    617     }
    618     KitSlice script_text = {.s = (const char*)sbytes, .len = slen};
    619     KitStatus prc = kit_link_script_parse(&ctx, script_text, &script);
    620     free(sbytes);
    621     if (prc != KIT_OK) {
    622       fprintf(stderr, "jit-runner: linker script parse failed: %s\n",
    623               script_path);
    624       free_compiler_target(c, kt);
    625       return 1;
    626     }
    627     opts.linker_script = script;
    628   }
    629 
    630   KitJit* jit = NULL;
    631   KitStatus rc = kit_link_session_new(c, &opts, &link);
    632   for (uint32_t i = 0; rc == KIT_OK && i < nobj; ++i)
    633     rc = kit_link_session_add_obj_bytes(link, kit_slice_cstr(obj_names[i]),
    634                                         &objs[i]);
    635   for (uint32_t i = 0; rc == KIT_OK && i < narc; ++i)
    636     rc = kit_link_session_add_archive_bytes(link, &archives[i]);
    637   if (rc == KIT_OK) rc = kit_link_session_jit(link, &jit);
    638   if (rc != KIT_OK || !jit) {
    639     kit_link_session_free(link);
    640     if (script) kit_link_script_free(&ctx, script);
    641     for (int i = 0; i < nbufs; i++) free(bufs[i]);
    642     free_compiler_target(c, kt);
    643     return 1;
    644   }
    645   kit_link_session_free(link);
    646   if (script) kit_link_script_free(&ctx, script);
    647   for (int i = 0; i < nbufs; i++) free(bufs[i]);
    648 
    649   /* Sanity: a never-defined sentinel must not be found (covers case 29). */
    650   if (kit_jit_lookup(jit, KIT_SLICE_LIT("__kit_test_sentinel__"))) {
    651     fprintf(stderr, "jit-runner: sentinel lookup unexpectedly non-NULL\n");
    652     kit_jit_run_dtors(jit);
    653     kit_jit_free(jit);
    654     free_compiler_target(c, kt);
    655     return 1;
    656   }
    657 
    658   /* --check-absent SYM: verify symbol was removed (e.g. by --gc-sections). */
    659   if (check_absent) {
    660     int absent_ok = kit_jit_lookup(jit, kit_slice_cstr(check_absent)) == NULL;
    661     if (!absent_ok)
    662       fprintf(stderr, "jit-runner: symbol '%s' present but expected absent\n",
    663               check_absent);
    664     kit_jit_run_dtors(jit);
    665     kit_jit_free(jit);
    666     free_compiler_target(c, kt);
    667     return absent_ok ? 0 : 1;
    668   }
    669 
    670   /* --check-present SYM: verify symbol survives the link / GC. */
    671   if (check_present) {
    672     int present_ok = kit_jit_lookup(jit, kit_slice_cstr(check_present)) != NULL;
    673     if (!present_ok)
    674       fprintf(stderr, "jit-runner: symbol '%s' missing but expected present\n",
    675               check_present);
    676     kit_jit_run_dtors(jit);
    677     kit_jit_free(jit);
    678     free_compiler_target(c, kt);
    679     return present_ok ? 0 : 1;
    680   }
    681 
    682   void* wasm_init = kit_jit_lookup(jit, KIT_SLICE_LIT("__kit_wasm_init"));
    683   void* entry = kit_jit_lookup(jit, kit_slice_cstr(entry_sym));
    684   int (*fn)(void) = entry;
    685 
    686   /* TLS local-exec setup.  Build the static TLS block and install the
    687    * target thread pointer immediately before entering JITed code. */
    688 #if defined(__aarch64__) || defined(__arm64__) || \
    689     (defined(__x86_64__) && defined(__linux__))
    690   static char tls_block[8192] __attribute__((aligned(16)));
    691 #if defined(__x86_64__) && defined(__linux__)
    692   unsigned long old_fs = 0;
    693   int restore_fs = 0;
    694 #endif
    695   {
    696     char* td_start = (char*)kit_jit_lookup(jit, KIT_SLICE_LIT("__tdata_start"));
    697     char* td_end = (char*)kit_jit_lookup(jit, KIT_SLICE_LIT("__tdata_end"));
    698     unsigned long bs_n = (unsigned long)(unsigned long long)kit_jit_lookup(
    699         jit, KIT_SLICE_LIT("__tbss_size"));
    700     if (td_start && td_end) {
    701       unsigned long td_n = (unsigned long)(td_end - td_start);
    702       unsigned long i;
    703       if (td_n == 0 && bs_n == 0) goto no_static_tls;
    704       /* Plain loops at -O0 stay loops; do NOT use memcpy/memset
    705        * here — those go through dyld's stub binder on first
    706        * call and clobber TPIDR_EL0. */
    707 #if defined(__x86_64__) && defined(__linux__)
    708       char* tcb = tls_block + sizeof(tls_block) - 64;
    709       char* tls = tcb - (td_n + bs_n);
    710       *(void**)tcb = tcb;
    711       for (i = 0; i < td_n; ++i) tls[i] = td_start[i];
    712       for (i = 0; i < bs_n; ++i) tls[td_n + i] = 0;
    713       if (x64_arch_prctl_raw(0x1003, (unsigned long)&old_fs) == 0 &&
    714           x64_arch_prctl_raw(0x1002, (unsigned long)tcb) == 0) {
    715         restore_fs = 1;
    716       }
    717 #else
    718       for (i = 0; i < td_n; ++i) tls_block[16 + i] = td_start[i];
    719       for (i = 0; i < bs_n; ++i) tls_block[16 + td_n + i] = 0;
    720 #endif
    721     }
    722   no_static_tls:;
    723   }
    724 #endif
    725 
    726   int result;
    727   if (wasm_init && entry) {
    728     WasmRunnerInstanceAlloc alloc;
    729     if (wasm_runner_make_instance(jit, &alloc) != 0) {
    730       kit_jit_run_dtors(jit);
    731       kit_jit_free(jit);
    732       free_compiler_target(c, kt);
    733       return 1;
    734     }
    735     {
    736       KitStatus bind_st =
    737           kit_wasm_bind_host_imports(c, jit, (KitWasmInstance*)alloc.instance,
    738                                      NULL, 0, wasm_runner_resolve_import, NULL);
    739       if (bind_st != KIT_OK) {
    740         fprintf(stderr, "jit-runner: wasm host import %s\n",
    741                 bind_st == KIT_NOT_FOUND
    742                     ? "unresolved"
    743                     : (bind_st == KIT_UNSUPPORTED ? "kind unsupported"
    744                                                   : "bind failed"));
    745         wasm_runner_free_instance(&alloc);
    746         kit_jit_run_dtors(jit);
    747         kit_jit_free(jit);
    748         free_compiler_target(c, kt);
    749         return 1;
    750       }
    751     }
    752     ((WasmRunnerInitFn)wasm_init)(alloc.instance);
    753     result = ((WasmRunnerMainFn)entry)(alloc.instance);
    754     wasm_runner_free_instance(&alloc);
    755   } else if (fn) {
    756 #if defined(__aarch64__) || defined(__arm64__)
    757     result = call_with_aarch64_tls(fn, tls_block);
    758 #else
    759     result = fn();
    760 #endif
    761 #if defined(__x86_64__) && defined(__linux__)
    762     if (restore_fs) (void)x64_arch_prctl_raw(0x1002, old_fs);
    763 #endif
    764   } else {
    765     result = 1;
    766   }
    767 
    768 #if defined(__x86_64__) && defined(__linux__)
    769   if (restore_fs)
    770     (void)x64_arch_prctl_raw(
    771         0x1002, (unsigned long)(tls_block + sizeof(tls_block) - 64));
    772 #endif
    773   kit_jit_run_dtors(jit);
    774 #if defined(__x86_64__) && defined(__linux__)
    775   if (restore_fs) (void)x64_arch_prctl_raw(0x1002, old_fs);
    776 #endif
    777 
    778   if (result == 0) {
    779     int (*post)(void) = kit_jit_lookup(jit, KIT_SLICE_LIT("test_post_fini"));
    780     if (post) result = post();
    781   }
    782 
    783   kit_jit_free(jit);
    784   free_compiler_target(c, kt);
    785   return result;
    786 }