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 }