parse_runner.c (17880B)
1 /* parse-runner — file-driven C front-end test runner. 2 * 3 * parse-runner --emit FILE.c OUT.o # full pipeline → ELF .o 4 * parse-runner --emit-c FILE.c OUT.c # full pipeline → C source via 5 * --emit=c CGTarget (Phase 1 C 6 * backend; see doc/CBACKEND.md) parse-runner --jit FILE.c # full 7 * pipeline → JIT, call test_main 8 * 9 * Exclusively uses the public kit.h surface: this is the same path real 10 * driver consumers take. Built once; the shell runner walks 11 * `test/parse/cases` for `.c` files and invokes one of the two modes per 12 * case. The JIT 13 * mode's exit status is `test_main`'s return value (mod 256); the emit 14 * mode's exit status is 0 on success and nonzero on any failure (the 15 * shell harness compares the resulting .o against the expected exit code 16 * via the cg/link harness binaries kit-roundtrip / link-exe-runner / 17 * jit-runner). 18 * 19 * The execmem boilerplate mirrors test/cg/harness/cg_runner.c — strict 20 * W^X dual mapping on Apple/Linux, single mapping elsewhere. */ 21 22 #ifndef _GNU_SOURCE 23 #define _GNU_SOURCE 24 #endif 25 26 #include <fcntl.h> 27 #include <kit/compile.h> 28 #include <kit/core.h> 29 #include <kit/jit.h> 30 #include <kit/link.h> 31 #include <stdarg.h> 32 #include <stdint.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <sys/mman.h> 37 #include <sys/stat.h> 38 #include <unistd.h> 39 40 #include "lib/kit_test_target.h" 41 42 /* ---- env: heap, diag ---- */ 43 44 static void* h_alloc(KitHeap* h, size_t n, size_t a) { 45 (void)h; 46 (void)a; 47 return n ? malloc(n) : NULL; 48 } 49 static void* h_realloc(KitHeap* h, void* p, size_t o, size_t n, size_t a) { 50 (void)h; 51 (void)o; 52 (void)a; 53 return realloc(p, n); 54 } 55 static void h_free(KitHeap* h, void* p, size_t n) { 56 (void)h; 57 (void)n; 58 free(p); 59 } 60 static KitHeap g_heap = {h_alloc, h_realloc, h_free, NULL}; 61 62 static void diag_emit(KitDiagSink* s, KitDiagKind k, KitSrcLoc loc, 63 const char* fmt, va_list ap) { 64 static const char* names[] = {"note", "warning", "error", "fatal"}; 65 (void)s; 66 fprintf(stderr, "[%u]:%u:%u: %s: ", loc.file_id, loc.line, loc.col, names[k]); 67 vfprintf(stderr, fmt, ap); 68 fputc('\n', stderr); 69 } 70 static KitDiagSink g_diag = {diag_emit, NULL, 0, 0}; 71 72 /* ---- env: execmem (W^X) — copied verbatim from cg_runner ---- */ 73 74 #if defined(__APPLE__) 75 #include <mach/mach.h> 76 #include <mach/mach_vm.h> 77 #define XM_DUAL_APPLE 1 78 #else 79 #define XM_DUAL_APPLE 0 80 #endif 81 #if defined(__linux__) 82 #include <sys/syscall.h> 83 #define XM_DUAL_LINUX 1 84 #else 85 #define XM_DUAL_LINUX 0 86 #endif 87 88 static int xm_to_posix(int p) { 89 int q = 0; 90 if (p & KIT_PROT_READ) q |= PROT_READ; 91 if (p & KIT_PROT_WRITE) q |= PROT_WRITE; 92 if (p & KIT_PROT_EXEC) q |= PROT_EXEC; 93 return q; 94 } 95 #if XM_DUAL_LINUX && defined(__x86_64__) && defined(MAP_32BIT) 96 #define XM_MAP_32BIT MAP_32BIT 97 static uintptr_t g_xm_low_runtime_hint = 0x40000000u; 98 static void* xm_low_runtime_hint(size_t n) { 99 uintptr_t p = g_xm_low_runtime_hint; 100 uintptr_t step = (uintptr_t)((n + 0xffffu) & ~(size_t)0xffffu); 101 if (step < 0x10000u) step = 0x10000u; 102 g_xm_low_runtime_hint = p + step + 0x10000u; 103 if (g_xm_low_runtime_hint > 0x78000000u) g_xm_low_runtime_hint = 0x40000000u; 104 return (void*)p; 105 } 106 #elif XM_DUAL_LINUX 107 #define XM_MAP_32BIT 0 108 static void* xm_low_runtime_hint(size_t n) { 109 (void)n; 110 return NULL; 111 } 112 #endif 113 typedef struct XmTok { 114 void* w; 115 void* r; 116 size_t n; 117 } XmTok; 118 static KitStatus xm_reserve_single(size_t n, KitExecMemRegion* out) { 119 void* p = 120 mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); 121 if (p == MAP_FAILED) return KIT_NOMEM; 122 out->write = out->runtime = p; 123 out->size = n; 124 out->token = NULL; 125 return KIT_OK; 126 } 127 static KitStatus xm_reserve(void* u, size_t n, int p, KitExecMemRegion* out) { 128 (void)u; 129 if (!out || !n) return KIT_INVALID; 130 if (!(p & KIT_PROT_EXEC)) return xm_reserve_single(n, out); 131 #if XM_DUAL_APPLE 132 { 133 void* w = 134 mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); 135 mach_vm_address_t r = 0; 136 vm_prot_t cur = 0, max = 0; 137 XmTok* tok; 138 if (w == MAP_FAILED) return KIT_NOMEM; 139 if (mach_vm_remap(mach_task_self(), &r, (mach_vm_size_t)n, 0, 140 VM_FLAGS_ANYWHERE, mach_task_self(), 141 (mach_vm_address_t)(uintptr_t)w, FALSE, &cur, &max, 142 VM_INHERIT_NONE) != KERN_SUCCESS) { 143 munmap(w, n); 144 return KIT_NOMEM; 145 } 146 if (mprotect((void*)(uintptr_t)r, n, PROT_READ) != 0) { 147 munmap((void*)(uintptr_t)r, n); 148 munmap(w, n); 149 return KIT_NOMEM; 150 } 151 tok = (XmTok*)malloc(sizeof(*tok)); 152 if (!tok) { 153 munmap((void*)(uintptr_t)r, n); 154 munmap(w, n); 155 return KIT_NOMEM; 156 } 157 tok->w = w; 158 tok->r = (void*)(uintptr_t)r; 159 tok->n = n; 160 out->write = w; 161 out->runtime = (void*)(uintptr_t)r; 162 out->size = n; 163 out->token = tok; 164 return KIT_OK; 165 } 166 #elif XM_DUAL_LINUX 167 { 168 int fd = (int)syscall(SYS_memfd_create, "kit-jit-test", 0u); 169 void *w, *r; 170 XmTok* tok; 171 if (fd < 0) return KIT_NOMEM; 172 if (ftruncate(fd, (off_t)n) != 0) { 173 close(fd); 174 return KIT_NOMEM; 175 } 176 w = mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_SHARED | XM_MAP_32BIT, fd, 0); 177 if (w == MAP_FAILED) { 178 close(fd); 179 return KIT_NOMEM; 180 } 181 r = mmap(xm_low_runtime_hint(n), n, PROT_READ, MAP_SHARED | XM_MAP_32BIT, 182 fd, 0); 183 close(fd); 184 if (r == MAP_FAILED) { 185 munmap(w, n); 186 return KIT_NOMEM; 187 } 188 tok = (XmTok*)malloc(sizeof(*tok)); 189 if (!tok) { 190 munmap(r, n); 191 munmap(w, n); 192 return KIT_NOMEM; 193 } 194 tok->w = w; 195 tok->r = r; 196 tok->n = n; 197 out->write = w; 198 out->runtime = r; 199 out->size = n; 200 out->token = tok; 201 return KIT_OK; 202 } 203 #else 204 return xm_reserve_single(n, out); 205 #endif 206 } 207 static KitStatus xm_protect(void* u, void* a, size_t n, int p) { 208 (void)u; 209 return mprotect(a, n, xm_to_posix(p)) == 0 ? KIT_OK : KIT_IO; 210 } 211 static void xm_release(void* u, KitExecMemRegion* region) { 212 (void)u; 213 if (!region || !region->size) return; 214 if (region->token) { 215 XmTok* tok = (XmTok*)region->token; 216 if (tok->r && tok->r != tok->w) munmap(tok->r, tok->n); 217 if (tok->w) munmap(tok->w, tok->n); 218 free(tok); 219 } else if (region->write) { 220 munmap(region->write, region->size); 221 } 222 region->write = region->runtime = NULL; 223 region->size = 0; 224 region->token = NULL; 225 } 226 static void xm_flush(void* u, void* a, size_t n) { 227 (void)u; 228 #if defined(__aarch64__) || defined(__arm__) || defined(__riscv) 229 #if defined(__riscv) 230 __asm__ __volatile__("fence.i" ::: "memory"); 231 #endif 232 __builtin___clear_cache((char*)a, (char*)a + n); 233 #else 234 (void)a; 235 (void)n; 236 #endif 237 } 238 static KitExecMem g_execmem = { 239 16 * 1024, xm_reserve, xm_protect, xm_release, xm_flush, NULL, 240 }; 241 242 /* ---- helpers ---- */ 243 244 static void target_from_env(KitTargetSpec* t) { 245 if (kit_test_target_init(t) != 0) { 246 fprintf(stderr, "parse-runner: kit_test_target_init failed\n"); 247 exit(2); 248 } 249 } 250 251 static int read_file(const char* path, uint8_t** out, size_t* out_len) { 252 FILE* f = fopen(path, "rb"); 253 long n; 254 uint8_t* buf; 255 size_t got; 256 if (!f) return 1; 257 if (fseek(f, 0, SEEK_END) != 0) { 258 fclose(f); 259 return 1; 260 } 261 n = ftell(f); 262 if (n < 0 || fseek(f, 0, SEEK_SET) != 0) { 263 fclose(f); 264 return 1; 265 } 266 buf = (uint8_t*)malloc((size_t)n + 1); 267 if (!buf) { 268 fclose(f); 269 return 1; 270 } 271 got = fread(buf, 1, (size_t)n, f); 272 fclose(f); 273 if (got != (size_t)n) { 274 free(buf); 275 return 1; 276 } 277 buf[n] = 0; 278 *out = buf; 279 *out_len = (size_t)n; 280 return 0; 281 } 282 283 static KitStatus test_read_all(void* user, const char* path, KitFileData* out) { 284 uint8_t* data = NULL; 285 size_t len = 0; 286 (void)user; 287 if (!out || read_file(path, &data, &len) != 0) return KIT_IO; 288 out->data = data; 289 out->size = len; 290 out->token = data; 291 return KIT_OK; 292 } 293 294 static void test_release(void* user, KitFileData* d) { 295 (void)user; 296 if (!d) return; 297 free(d->token); 298 d->data = NULL; 299 d->size = 0; 300 d->token = NULL; 301 } 302 303 static KitFileIO g_file_io = {test_read_all, test_release, NULL, NULL}; 304 305 static void ctx_init(KitContext* ctx) { 306 memset(ctx, 0, sizeof *ctx); 307 ctx->heap = &g_heap; 308 ctx->diag = &g_diag; 309 ctx->file_io = &g_file_io; 310 ctx->now = -1; 311 } 312 313 static KitStatus compiler_new_for_target(KitTargetSpec spec, KitContext* ctx, 314 KitTarget** target_out, 315 KitCompiler** compiler_out) { 316 KitTargetOptions opts; 317 KitStatus st; 318 if (target_out) *target_out = NULL; 319 if (compiler_out) *compiler_out = NULL; 320 memset(&opts, 0, sizeof opts); 321 opts.spec = spec; 322 /* Thread the ISA/ABI the resolver needs to honor the spec's float_abi (rv32 323 * -> ilp32f), so the emitted object's e_flags match the bare harness. */ 324 kit_test_target_isa_abi(&spec, &opts.isa, &opts.abi); 325 st = kit_target_new(ctx, &opts, target_out); 326 if (st != KIT_OK) return st; 327 st = kit_compiler_new(*target_out, ctx, compiler_out); 328 if (st != KIT_OK) { 329 kit_target_free(*target_out); 330 *target_out = NULL; 331 } 332 return st; 333 } 334 335 static int opt_level_from_env(void) { 336 const char* s = getenv("KIT_OPT_LEVEL"); 337 if (!s || !*s) return 0; 338 if (!strcmp(s, "0")) return 0; 339 if (!strcmp(s, "1")) return 1; 340 if (!strcmp(s, "2")) return 2; 341 fprintf(stderr, "parse-runner: invalid KIT_OPT_LEVEL=%s\n", s); 342 exit(2); 343 } 344 345 static void add_test_system_includes(KitPreprocessOptions* pp) { 346 static const char* dirs[] = {"rt/include"}; 347 pp->system_include_dirs = dirs; 348 pp->nsystem_include_dirs = 1; 349 } 350 351 static int add_runtime_archive(KitLinkArchiveInput* rt_archive, 352 uint8_t** rt_data_out) { 353 uint8_t* data = NULL; 354 size_t len = 0; 355 const char* arch = kit_test_arch_name(); 356 const char* path = "build/rt/aarch64-linux/libkit_rt.a"; 357 if (!strcmp(arch, "rv64") || !strcmp(arch, "riscv64")) 358 path = "build/rt/riscv64-linux/libkit_rt.a"; 359 else if (!strcmp(arch, "x64") || !strcmp(arch, "x86_64") || 360 !strcmp(arch, "amd64")) 361 path = "build/rt/x86_64-linux/libkit_rt.a"; 362 if (read_file(path, &data, &len) != 0) return 0; 363 memset(rt_archive, 0, sizeof *rt_archive); 364 rt_archive->name = kit_slice_cstr(path); 365 rt_archive->bytes.data = data; 366 rt_archive->bytes.len = len; 367 *rt_data_out = data; 368 return 1; 369 } 370 371 static KitStatus compile_c_obj(KitCompiler* c, const KitCCompileOptions* opts, 372 const KitPreprocessOptions* pp, KitSlice name, 373 const KitSlice* in, KitObjBuilder** out) { 374 KitCompileSessionOptions sopts; 375 KitCompileSession* session = NULL; 376 KitSourceInput sin; 377 KitStatus st; 378 memset(&sopts, 0, sizeof(sopts)); 379 sopts.lang = KIT_LANG_C; 380 sopts.compile.code = opts->code; 381 sopts.compile.diagnostics = opts->diagnostics; 382 if (pp) sopts.compile.preprocess = *pp; 383 memset(&sin, 0, sizeof(sin)); 384 sin.name = name; 385 sin.bytes = *in; 386 sin.lang = KIT_LANG_C; 387 st = kit_compile_session_new(c, &sopts, &session); 388 if (st == KIT_OK) st = kit_compile_session_compile(session, &sin, out); 389 kit_compile_session_free(session); 390 return st; 391 } 392 393 static KitStatus compile_c_emit(KitCompiler* c, const KitCCompileOptions* opts, 394 const KitPreprocessOptions* pp, KitSlice name, 395 const KitSlice* in, KitWriter* w) { 396 KitObjBuilder* ob = NULL; 397 KitStatus st = compile_c_obj(c, opts, pp, name, in, &ob); 398 if (st == KIT_OK && !opts->code.emit_c_source) 399 st = kit_obj_builder_emit(ob, w); 400 kit_obj_builder_free(ob); 401 return st; 402 } 403 404 /* ---- modes ---- */ 405 406 static int mode_emit_impl(const char* src_path, const char* out_path, 407 int emit_c) { 408 uint8_t* src = NULL; 409 size_t src_len = 0; 410 KitTargetSpec tgt; 411 KitContext ctx; 412 KitTarget* target = NULL; 413 KitCompiler* c = NULL; 414 KitSlice in; 415 KitCCompileOptions opts; 416 KitPreprocessOptions pp; 417 KitWriter* w = NULL; 418 int rc = 0; 419 size_t len = 0; 420 const uint8_t* data; 421 int fd; 422 423 if (read_file(src_path, &src, &src_len)) { 424 fprintf(stderr, "parse-runner: cannot read %s\n", src_path); 425 return 2; 426 } 427 target_from_env(&tgt); 428 ctx_init(&ctx); 429 if (compiler_new_for_target(tgt, &ctx, &target, &c) != KIT_OK || !c) { 430 free(src); 431 return 2; 432 } 433 memset(&in, 0, sizeof in); 434 in.data = src; 435 in.len = src_len; 436 437 memset(&opts, 0, sizeof opts); 438 /* --emit-c forces opt_level=0 inside CG anyway, but be explicit so the 439 * env-driven opt level doesn't surprise. */ 440 opts.code.opt_level = emit_c ? 0 : opt_level_from_env(); 441 memset(&pp, 0, sizeof pp); 442 add_test_system_includes(&pp); 443 444 (void)kit_writer_mem(&g_heap, &w); 445 if (emit_c) { 446 opts.code.emit_c_source = true; 447 opts.code.c_source_writer = w; 448 } 449 if (compile_c_emit(c, &opts, &pp, kit_slice_cstr(src_path), &in, w) != 450 KIT_OK) { 451 kit_writer_close(w); 452 kit_compiler_free(c); 453 kit_target_free(target); 454 free(src); 455 return 1; 456 } 457 458 data = kit_writer_mem_bytes(w, &len); 459 fd = open(out_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); 460 if (fd < 0) { 461 perror(out_path); 462 rc = 2; 463 } else { 464 size_t off = 0; 465 while (off < len) { 466 ssize_t k = write(fd, data + off, len - off); 467 if (k <= 0) { 468 perror("write"); 469 rc = 2; 470 break; 471 } 472 off += (size_t)k; 473 } 474 close(fd); 475 } 476 kit_writer_close(w); 477 kit_compiler_free(c); 478 kit_target_free(target); 479 free(src); 480 return rc; 481 } 482 483 /* On AArch64 host, set up TLS Local-Exec image before invoking JITed 484 * code. Mirrors test/cg/harness/cg_runner.c: msr → call() must be 485 * back-to-back with no libc invocations in between (Darwin libc clobbers 486 * TPIDR_EL0 via the dyld stub binder). Cases that don't use TLS see the 487 * lookups fail and the block stays zeroed — harmless. */ 488 #if defined(__aarch64__) || defined(__arm64__) 489 static char g_tls_block[8192] __attribute__((aligned(16))); 490 491 __attribute__((noinline, no_sanitize("address", "undefined"))) static int 492 call_with_aarch64_tls(int (*fn)(void), void* tls_block) { 493 void* old_tp; 494 int result; 495 __asm__ volatile("mrs %0, tpidr_el0" : "=r"(old_tp)::"memory"); 496 __asm__ volatile("msr tpidr_el0, %0" ::"r"(tls_block) : "memory"); 497 result = fn(); 498 __asm__ volatile("msr tpidr_el0, %0" ::"r"(old_tp) : "memory"); 499 return result; 500 } 501 #endif 502 503 static int mode_jit(const char* src_path) { 504 uint8_t* src = NULL; 505 size_t src_len = 0; 506 KitTargetSpec tgt; 507 KitContext ctx; 508 KitTarget* target = NULL; 509 KitCompiler* c = NULL; 510 KitSlice in; 511 KitCCompileOptions opts; 512 KitPreprocessOptions pp; 513 KitObjBuilder* ob = NULL; 514 KitLinkArchiveInput rt_archive; 515 KitLinkSessionOptions lopts; 516 KitLinkSession* link = NULL; 517 KitJitHost jhost; 518 uint8_t* rt_data = NULL; 519 KitJit* jit = NULL; 520 int (*fn)(void); 521 int result; 522 523 if (read_file(src_path, &src, &src_len)) { 524 fprintf(stderr, "parse-runner: cannot read %s\n", src_path); 525 return 2; 526 } 527 target_from_env(&tgt); 528 ctx_init(&ctx); 529 if (compiler_new_for_target(tgt, &ctx, &target, &c) != KIT_OK || !c) { 530 free(src); 531 return 2; 532 } 533 memset(&in, 0, sizeof in); 534 in.data = src; 535 in.len = src_len; 536 memset(&opts, 0, sizeof opts); 537 opts.code.opt_level = opt_level_from_env(); 538 memset(&pp, 0, sizeof pp); 539 add_test_system_includes(&pp); 540 541 if (compile_c_obj(c, &opts, &pp, kit_slice_cstr(src_path), &in, &ob) != 542 KIT_OK || 543 !ob) { 544 kit_compiler_free(c); 545 kit_target_free(target); 546 free(src); 547 return 1; 548 } 549 550 memset(&lopts, 0, sizeof lopts); 551 lopts.output_kind = KIT_LINK_OUTPUT_JIT; 552 lopts.entry = KIT_SLICE_LIT("test_main"); 553 554 memset(&jhost, 0, sizeof jhost); 555 jhost.execmem = &g_execmem; 556 lopts.jit_host = &jhost; 557 558 if (!add_runtime_archive(&rt_archive, &rt_data)) { 559 kit_compiler_free(c); 560 kit_target_free(target); 561 free(src); 562 return 1; 563 } 564 if (kit_link_session_new(c, &lopts, &link) != KIT_OK || 565 kit_link_session_add_obj(link, ob) != KIT_OK || 566 kit_link_session_add_archive_bytes(link, &rt_archive) != KIT_OK || 567 kit_link_session_jit(link, &jit) != KIT_OK || !jit) { 568 kit_link_session_free(link); 569 kit_compiler_free(c); 570 kit_target_free(target); 571 free(rt_data); 572 free(src); 573 return 1; 574 } 575 kit_link_session_free(link); 576 577 fn = (int (*)(void))kit_jit_lookup(jit, KIT_SLICE_LIT("test_main")); 578 579 #if defined(__aarch64__) || defined(__arm64__) 580 { 581 char* td_start = (char*)kit_jit_lookup(jit, KIT_SLICE_LIT("__tdata_start")); 582 char* td_end = (char*)kit_jit_lookup(jit, KIT_SLICE_LIT("__tdata_end")); 583 unsigned long bs_n = (unsigned long)(unsigned long long)kit_jit_lookup( 584 jit, KIT_SLICE_LIT("__tbss_size")); 585 if (td_start && td_end) { 586 unsigned long td_n = (unsigned long)(td_end - td_start); 587 unsigned long i; 588 for (i = 0; i < td_n; ++i) g_tls_block[16 + i] = td_start[i]; 589 for (i = 0; i < bs_n; ++i) g_tls_block[16 + td_n + i] = 0; 590 } 591 } 592 #endif 593 594 if (fn) { 595 #if defined(__aarch64__) || defined(__arm64__) 596 result = call_with_aarch64_tls(fn, g_tls_block); 597 #else 598 result = fn(); 599 #endif 600 } else { 601 result = 1; 602 } 603 604 kit_jit_free(jit); 605 kit_compiler_free(c); 606 kit_target_free(target); 607 free(rt_data); 608 free(src); 609 return result; 610 } 611 612 static int usage(void) { 613 fprintf(stderr, 614 "usage: parse-runner --emit FILE.c OUT.o\n" 615 " parse-runner --emit-c FILE.c OUT.c\n" 616 " parse-runner --jit FILE.c\n"); 617 return 2; 618 } 619 620 int main(int argc, char** argv) { 621 long ps = sysconf(_SC_PAGESIZE); 622 if (ps > 0) g_execmem.page_size = (size_t)ps; 623 if (argc < 2) return usage(); 624 if (!strcmp(argv[1], "--emit") && argc == 4) 625 return mode_emit_impl(argv[2], argv[3], 0); 626 if (!strcmp(argv[1], "--emit-c") && argc == 4) 627 return mode_emit_impl(argv[2], argv[3], 1); 628 if (!strcmp(argv[1], "--jit") && argc == 3) return mode_jit(argv[2]); 629 return usage(); 630 }