parse_runner.c (17669B)
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 st = kit_target_new(ctx, &opts, target_out); 323 if (st != KIT_OK) return st; 324 st = kit_compiler_new(*target_out, ctx, compiler_out); 325 if (st != KIT_OK) { 326 kit_target_free(*target_out); 327 *target_out = NULL; 328 } 329 return st; 330 } 331 332 static int opt_level_from_env(void) { 333 const char* s = getenv("KIT_OPT_LEVEL"); 334 if (!s || !*s) return 0; 335 if (!strcmp(s, "0")) return 0; 336 if (!strcmp(s, "1")) return 1; 337 if (!strcmp(s, "2")) return 2; 338 fprintf(stderr, "parse-runner: invalid KIT_OPT_LEVEL=%s\n", s); 339 exit(2); 340 } 341 342 static void add_test_system_includes(KitPreprocessOptions* pp) { 343 static const char* dirs[] = {"rt/include"}; 344 pp->system_include_dirs = dirs; 345 pp->nsystem_include_dirs = 1; 346 } 347 348 static int add_runtime_archive(KitLinkArchiveInput* rt_archive, 349 uint8_t** rt_data_out) { 350 uint8_t* data = NULL; 351 size_t len = 0; 352 const char* arch = kit_test_arch_name(); 353 const char* path = "build/rt/aarch64-linux/libkit_rt.a"; 354 if (!strcmp(arch, "rv64") || !strcmp(arch, "riscv64")) 355 path = "build/rt/riscv64-linux/libkit_rt.a"; 356 else if (!strcmp(arch, "x64") || !strcmp(arch, "x86_64") || 357 !strcmp(arch, "amd64")) 358 path = "build/rt/x86_64-linux/libkit_rt.a"; 359 if (read_file(path, &data, &len) != 0) return 0; 360 memset(rt_archive, 0, sizeof *rt_archive); 361 rt_archive->name = kit_slice_cstr(path); 362 rt_archive->bytes.data = data; 363 rt_archive->bytes.len = len; 364 *rt_data_out = data; 365 return 1; 366 } 367 368 static KitStatus compile_c_obj(KitCompiler* c, const KitCCompileOptions* opts, 369 const KitPreprocessOptions* pp, KitSlice name, 370 const KitSlice* in, KitObjBuilder** out) { 371 KitCompileSessionOptions sopts; 372 KitCompileSession* session = NULL; 373 KitSourceInput sin; 374 KitStatus st; 375 memset(&sopts, 0, sizeof(sopts)); 376 sopts.lang = KIT_LANG_C; 377 sopts.compile.code = opts->code; 378 sopts.compile.diagnostics = opts->diagnostics; 379 if (pp) sopts.compile.preprocess = *pp; 380 memset(&sin, 0, sizeof(sin)); 381 sin.name = name; 382 sin.bytes = *in; 383 sin.lang = KIT_LANG_C; 384 st = kit_compile_session_new(c, &sopts, &session); 385 if (st == KIT_OK) st = kit_compile_session_compile(session, &sin, out); 386 kit_compile_session_free(session); 387 return st; 388 } 389 390 static KitStatus compile_c_emit(KitCompiler* c, const KitCCompileOptions* opts, 391 const KitPreprocessOptions* pp, KitSlice name, 392 const KitSlice* in, KitWriter* w) { 393 KitObjBuilder* ob = NULL; 394 KitStatus st = compile_c_obj(c, opts, pp, name, in, &ob); 395 if (st == KIT_OK && !opts->code.emit_c_source) 396 st = kit_obj_builder_emit(ob, w); 397 kit_obj_builder_free(ob); 398 return st; 399 } 400 401 /* ---- modes ---- */ 402 403 static int mode_emit_impl(const char* src_path, const char* out_path, 404 int emit_c) { 405 uint8_t* src = NULL; 406 size_t src_len = 0; 407 KitTargetSpec tgt; 408 KitContext ctx; 409 KitTarget* target = NULL; 410 KitCompiler* c = NULL; 411 KitSlice in; 412 KitCCompileOptions opts; 413 KitPreprocessOptions pp; 414 KitWriter* w = NULL; 415 int rc = 0; 416 size_t len = 0; 417 const uint8_t* data; 418 int fd; 419 420 if (read_file(src_path, &src, &src_len)) { 421 fprintf(stderr, "parse-runner: cannot read %s\n", src_path); 422 return 2; 423 } 424 target_from_env(&tgt); 425 ctx_init(&ctx); 426 if (compiler_new_for_target(tgt, &ctx, &target, &c) != KIT_OK || !c) { 427 free(src); 428 return 2; 429 } 430 memset(&in, 0, sizeof in); 431 in.data = src; 432 in.len = src_len; 433 434 memset(&opts, 0, sizeof opts); 435 /* --emit-c forces opt_level=0 inside CG anyway, but be explicit so the 436 * env-driven opt level doesn't surprise. */ 437 opts.code.opt_level = emit_c ? 0 : opt_level_from_env(); 438 memset(&pp, 0, sizeof pp); 439 add_test_system_includes(&pp); 440 441 (void)kit_writer_mem(&g_heap, &w); 442 if (emit_c) { 443 opts.code.emit_c_source = true; 444 opts.code.c_source_writer = w; 445 } 446 if (compile_c_emit(c, &opts, &pp, kit_slice_cstr(src_path), &in, w) != 447 KIT_OK) { 448 kit_writer_close(w); 449 kit_compiler_free(c); 450 kit_target_free(target); 451 free(src); 452 return 1; 453 } 454 455 data = kit_writer_mem_bytes(w, &len); 456 fd = open(out_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); 457 if (fd < 0) { 458 perror(out_path); 459 rc = 2; 460 } else { 461 size_t off = 0; 462 while (off < len) { 463 ssize_t k = write(fd, data + off, len - off); 464 if (k <= 0) { 465 perror("write"); 466 rc = 2; 467 break; 468 } 469 off += (size_t)k; 470 } 471 close(fd); 472 } 473 kit_writer_close(w); 474 kit_compiler_free(c); 475 kit_target_free(target); 476 free(src); 477 return rc; 478 } 479 480 /* On AArch64 host, set up TLS Local-Exec image before invoking JITed 481 * code. Mirrors test/cg/harness/cg_runner.c: msr → call() must be 482 * back-to-back with no libc invocations in between (Darwin libc clobbers 483 * TPIDR_EL0 via the dyld stub binder). Cases that don't use TLS see the 484 * lookups fail and the block stays zeroed — harmless. */ 485 #if defined(__aarch64__) || defined(__arm64__) 486 static char g_tls_block[8192] __attribute__((aligned(16))); 487 488 __attribute__((noinline, no_sanitize("address", "undefined"))) static int 489 call_with_aarch64_tls(int (*fn)(void), void* tls_block) { 490 void* old_tp; 491 int result; 492 __asm__ volatile("mrs %0, tpidr_el0" : "=r"(old_tp)::"memory"); 493 __asm__ volatile("msr tpidr_el0, %0" ::"r"(tls_block) : "memory"); 494 result = fn(); 495 __asm__ volatile("msr tpidr_el0, %0" ::"r"(old_tp) : "memory"); 496 return result; 497 } 498 #endif 499 500 static int mode_jit(const char* src_path) { 501 uint8_t* src = NULL; 502 size_t src_len = 0; 503 KitTargetSpec tgt; 504 KitContext ctx; 505 KitTarget* target = NULL; 506 KitCompiler* c = NULL; 507 KitSlice in; 508 KitCCompileOptions opts; 509 KitPreprocessOptions pp; 510 KitObjBuilder* ob = NULL; 511 KitLinkArchiveInput rt_archive; 512 KitLinkSessionOptions lopts; 513 KitLinkSession* link = NULL; 514 KitJitHost jhost; 515 uint8_t* rt_data = NULL; 516 KitJit* jit = NULL; 517 int (*fn)(void); 518 int result; 519 520 if (read_file(src_path, &src, &src_len)) { 521 fprintf(stderr, "parse-runner: cannot read %s\n", src_path); 522 return 2; 523 } 524 target_from_env(&tgt); 525 ctx_init(&ctx); 526 if (compiler_new_for_target(tgt, &ctx, &target, &c) != KIT_OK || !c) { 527 free(src); 528 return 2; 529 } 530 memset(&in, 0, sizeof in); 531 in.data = src; 532 in.len = src_len; 533 memset(&opts, 0, sizeof opts); 534 opts.code.opt_level = opt_level_from_env(); 535 memset(&pp, 0, sizeof pp); 536 add_test_system_includes(&pp); 537 538 if (compile_c_obj(c, &opts, &pp, kit_slice_cstr(src_path), &in, &ob) != 539 KIT_OK || 540 !ob) { 541 kit_compiler_free(c); 542 kit_target_free(target); 543 free(src); 544 return 1; 545 } 546 547 memset(&lopts, 0, sizeof lopts); 548 lopts.output_kind = KIT_LINK_OUTPUT_JIT; 549 lopts.entry = KIT_SLICE_LIT("test_main"); 550 551 memset(&jhost, 0, sizeof jhost); 552 jhost.execmem = &g_execmem; 553 lopts.jit_host = &jhost; 554 555 if (!add_runtime_archive(&rt_archive, &rt_data)) { 556 kit_compiler_free(c); 557 kit_target_free(target); 558 free(src); 559 return 1; 560 } 561 if (kit_link_session_new(c, &lopts, &link) != KIT_OK || 562 kit_link_session_add_obj(link, ob) != KIT_OK || 563 kit_link_session_add_archive_bytes(link, &rt_archive) != KIT_OK || 564 kit_link_session_jit(link, &jit) != KIT_OK || !jit) { 565 kit_link_session_free(link); 566 kit_compiler_free(c); 567 kit_target_free(target); 568 free(rt_data); 569 free(src); 570 return 1; 571 } 572 kit_link_session_free(link); 573 574 fn = (int (*)(void))kit_jit_lookup(jit, KIT_SLICE_LIT("test_main")); 575 576 #if defined(__aarch64__) || defined(__arm64__) 577 { 578 char* td_start = (char*)kit_jit_lookup(jit, KIT_SLICE_LIT("__tdata_start")); 579 char* td_end = (char*)kit_jit_lookup(jit, KIT_SLICE_LIT("__tdata_end")); 580 unsigned long bs_n = (unsigned long)(unsigned long long)kit_jit_lookup( 581 jit, KIT_SLICE_LIT("__tbss_size")); 582 if (td_start && td_end) { 583 unsigned long td_n = (unsigned long)(td_end - td_start); 584 unsigned long i; 585 for (i = 0; i < td_n; ++i) g_tls_block[16 + i] = td_start[i]; 586 for (i = 0; i < bs_n; ++i) g_tls_block[16 + td_n + i] = 0; 587 } 588 } 589 #endif 590 591 if (fn) { 592 #if defined(__aarch64__) || defined(__arm64__) 593 result = call_with_aarch64_tls(fn, g_tls_block); 594 #else 595 result = fn(); 596 #endif 597 } else { 598 result = 1; 599 } 600 601 kit_jit_free(jit); 602 kit_compiler_free(c); 603 kit_target_free(target); 604 free(rt_data); 605 free(src); 606 return result; 607 } 608 609 static int usage(void) { 610 fprintf(stderr, 611 "usage: parse-runner --emit FILE.c OUT.o\n" 612 " parse-runner --emit-c FILE.c OUT.c\n" 613 " parse-runner --jit FILE.c\n"); 614 return 2; 615 } 616 617 int main(int argc, char** argv) { 618 long ps = sysconf(_SC_PAGESIZE); 619 if (ps > 0) g_execmem.page_size = (size_t)ps; 620 if (argc < 2) return usage(); 621 if (!strcmp(argv[1], "--emit") && argc == 4) 622 return mode_emit_impl(argv[2], argv[3], 0); 623 if (!strcmp(argv[1], "--emit-c") && argc == 4) 624 return mode_emit_impl(argv[2], argv[3], 1); 625 if (!strcmp(argv[1], "--jit") && argc == 3) return mode_jit(argv[2]); 626 return usage(); 627 }