run.c (40343B)
1 #include <kit/compile.h> 2 #include <kit/core.h> 3 #include <kit/interp.h> 4 #include <kit/jit.h> 5 #include <kit/link.h> 6 #include <stdint.h> 7 #include <stdlib.h> 8 #include <string.h> 9 10 #include "backtrace.h" 11 #include "cflags.h" 12 #include "driver.h" 13 #include "hosted.h" 14 #include "inputs.h" 15 #include "wasm_run.h" 16 17 /* `kit run` — JIT-compile one or more inputs and invoke the entry symbol 18 * (default `main`) in-process. Args after `--` are passed to the JITed 19 * program as argv. Mirrors the cc front-end for input shape (.c / - sources, 20 * .o objects, .a archives) and target/diagnostic flag surface; libkit's 21 * JIT path forces PIC regardless of `-fPIC`/`-fPIE`/`-mcmodel`. 22 * 23 * Native host-symbol fallback (so JITed C/Toy/object code can call libc) goes 24 * through driver_dlsym_resolver in driver/env.c. Wasm source inputs do not get 25 * that fallback; host access is controlled by the Wasm import policy. The 26 * driver returns whatever the entry returns, or 1 on a compile/link/lookup 27 * error. The native entry is invoked as `int(*)(int, char**)`. */ 28 29 #define RUN_TOOL "run" 30 31 typedef struct RunOptions { 32 DriverEnv* env; 33 size_t argv_bound; 34 35 int opt_level; 36 int no_jit; /* --no-jit: execute the entry via the IR interpreter */ 37 int debug_info; 38 int metrics; 39 int bench_time; 40 int warnings_are_errors; /* -Werror */ 41 uint32_t max_errors; /* -fmax-errors=N */ 42 const char* entry; /* -e, default "main" */ 43 const char* sysroot; /* --sysroot */ 44 int wants_hosted_libc; /* -lc */ 45 KitTargetSpec target; /* -target / host */ 46 DriverTargetFeatures target_features; 47 DriverHostedPlan hosted; 48 49 DriverCflags cf; 50 DriverInputs inputs; 51 DriverWasmRunOptions wasm; 52 53 char** prog_argv; /* args after `--` */ 54 uint32_t prog_argc; 55 } RunOptions; 56 57 /* Buffered metrics sink. The hot path (scope_begin/end/count called from 58 * libkit during compile/link/JIT) does no I/O: each event is appended to 59 * a segmented event log, and scopes maintain only a small live stack to 60 * carry the start_ns across to scope_end. All formatting and stderr writes 61 * happen once in run_metrics_finish at the very end of the run. 62 * 63 * Storage is a singly-linked list of fixed-size segments. Each segment is 64 * allocated once and never moves — geometric vector growth would amortize 65 * to O(log N) copies, but with a chunk list we get zero copies and the 66 * hot path is just an index increment + 24-byte store. We only ever drain 67 * the log sequentially, so a segment table is unnecessary. */ 68 69 #define RUN_METRIC_MAX_DEPTH 64u 70 #define RUN_METRIC_SEG_EVENTS 256u 71 72 typedef enum RunMetricEventKind { 73 RUN_METRIC_EVENT_SCOPE = 0, 74 RUN_METRIC_EVENT_COUNT = 1, 75 } RunMetricEventKind; 76 77 typedef struct RunMetricEvent { 78 const char* name; /* aliased; callers pass string literals */ 79 uint64_t value; /* SCOPE: elapsed_ns; COUNT: count value */ 80 uint16_t depth; /* nesting depth at emit time (0-based) */ 81 uint16_t kind; 82 } RunMetricEvent; 83 84 typedef struct RunMetricSeg { 85 struct RunMetricSeg* next; 86 uint32_t nused; 87 RunMetricEvent events[RUN_METRIC_SEG_EVENTS]; 88 } RunMetricSeg; 89 90 typedef struct RunMetricFrame { 91 const char* name; 92 uint64_t start_ns; 93 } RunMetricFrame; 94 95 typedef struct RunMetrics { 96 KitMetrics iface; 97 RunMetricFrame stack[RUN_METRIC_MAX_DEPTH]; 98 uint32_t depth; 99 int bench_time; 100 101 DriverEnv* env; 102 RunMetricSeg* head; 103 RunMetricSeg* tail; 104 int oom; /* sticky: drop further events after an allocation failure */ 105 } RunMetrics; 106 107 static void run_metrics_push_event(RunMetrics* m, RunMetricEventKind kind, 108 const char* name, uint64_t value, 109 uint32_t depth) { 110 RunMetricSeg* seg; 111 RunMetricEvent* ev; 112 if (m->oom) return; 113 seg = m->tail; 114 if (!seg || seg->nused == RUN_METRIC_SEG_EVENTS) { 115 RunMetricSeg* ns = driver_alloc_zeroed(m->env, sizeof(RunMetricSeg)); 116 if (!ns) { 117 m->oom = 1; 118 return; 119 } 120 if (m->tail) 121 m->tail->next = ns; 122 else 123 m->head = ns; 124 m->tail = ns; 125 seg = ns; 126 } 127 ev = &seg->events[seg->nused++]; 128 ev->name = name; 129 ev->value = value; 130 ev->depth = (uint16_t)depth; 131 ev->kind = (uint16_t)kind; 132 } 133 134 static void run_metrics_scope_begin(void* user, const char* name) { 135 RunMetrics* m = (RunMetrics*)user; 136 if (!m || m->depth >= RUN_METRIC_MAX_DEPTH) return; 137 m->stack[m->depth].name = name; 138 m->stack[m->depth].start_ns = driver_now_ns(); 139 m->depth++; 140 } 141 142 static void run_metrics_scope_end(void* user, const char* name) { 143 RunMetrics* m = (RunMetrics*)user; 144 RunMetricFrame f; 145 uint64_t end_ns; 146 uint64_t elapsed; 147 uint32_t depth; 148 const char* scope_name; 149 if (!m || m->depth == 0) return; 150 m->depth--; 151 depth = m->depth; 152 f = m->stack[depth]; 153 end_ns = driver_now_ns(); 154 elapsed = (end_ns >= f.start_ns) ? (end_ns - f.start_ns) : 0; 155 scope_name = f.name ? f.name : name; 156 run_metrics_push_event(m, RUN_METRIC_EVENT_SCOPE, scope_name, elapsed, depth); 157 } 158 159 static void run_metrics_count(void* user, const char* name, uint64_t value) { 160 RunMetrics* m = (RunMetrics*)user; 161 if (!m) return; 162 run_metrics_push_event(m, RUN_METRIC_EVENT_COUNT, name, value, m->depth); 163 } 164 165 static void run_metrics_init(RunMetrics* m, DriverEnv* env, int bench_time) { 166 m->iface.scope_begin = run_metrics_scope_begin; 167 m->iface.scope_end = run_metrics_scope_end; 168 m->iface.count = run_metrics_count; 169 m->iface.user = m; 170 m->depth = 0; 171 m->bench_time = bench_time; 172 m->env = env; 173 m->head = NULL; 174 m->tail = NULL; 175 m->oom = 0; 176 } 177 178 static void run_metrics_begin(RunMetrics* m, const char* name) { 179 if (m) m->iface.scope_begin(m->iface.user, name); 180 } 181 182 static void run_metrics_end(RunMetrics* m, const char* name) { 183 if (m) m->iface.scope_end(m->iface.user, name); 184 } 185 186 /* Close any still-open scopes (early-return paths emit synthesized ends), 187 * then walk the segmented event log in push order and format each event 188 * to stderr. Free segments after draining. Called exactly once per run. */ 189 static void run_metrics_finish(RunMetrics* m) { 190 RunMetricSeg* seg; 191 if (!m) return; 192 while (m->depth) { 193 const char* name = m->stack[m->depth - 1u].name; 194 run_metrics_scope_end(m, name); 195 } 196 for (seg = m->head; seg; seg = seg->next) { 197 uint32_t i; 198 for (i = 0; i < seg->nused; ++i) { 199 const RunMetricEvent* e = &seg->events[i]; 200 if (e->kind == RUN_METRIC_EVENT_SCOPE) { 201 if (m->bench_time) { 202 driver_logf("kit-run %.*s -- %.3f msec", 203 KIT_SLICE_ARG(kit_slice_cstr(e->name)), 204 (double)e->value / 1000000.0); 205 } else { 206 driver_logf("%*s%.*s %.3f ms", (int)((unsigned)e->depth * 2u), "", 207 KIT_SLICE_ARG(kit_slice_cstr(e->name)), 208 (double)e->value / 1000000.0); 209 } 210 } else { 211 driver_logf("%*s%.*s=%llu", (int)((unsigned)e->depth * 2u), "", 212 KIT_SLICE_ARG(kit_slice_cstr(e->name)), 213 (unsigned long long)e->value); 214 } 215 } 216 } 217 if (m->oom) { 218 driver_logf("(metrics: event buffer allocation failed; some events lost)"); 219 } 220 seg = m->head; 221 while (seg) { 222 RunMetricSeg* next = seg->next; 223 driver_free(m->env, seg, sizeof(RunMetricSeg)); 224 seg = next; 225 } 226 m->head = NULL; 227 m->tail = NULL; 228 } 229 230 static void run_bench_time(const char* name, uint64_t ns) { 231 driver_logf("kit-run %.*s -- %.3f msec", KIT_SLICE_ARG(kit_slice_cstr(name)), 232 (double)ns / 1000000.0); 233 } 234 235 static void run_usage(void) { 236 driver_errf(RUN_TOOL, "%.*s", 237 KIT_SLICE_ARG(KIT_SLICE_LIT( 238 "usage: kit run [options] inputs... [-- prog-arg...]\n" 239 " kit run --help for full option reference"))); 240 } 241 242 void driver_help_run(void) { 243 driver_printf( 244 "%.*s", 245 KIT_SLICE_ARG(KIT_SLICE_LIT( 246 "kit run — JIT-compile inputs and invoke the entry symbol " 247 "in-process\n" 248 "\n" 249 "USAGE\n" 250 " kit run [options] inputs... [-- prog-arg...]\n" 251 "\n" 252 "DESCRIPTION\n" 253 " Compiles and JIT-links every input, looks up the entry symbol\n" 254 " (default `main`), and calls it as `int(*)(int, char**)`. Args\n" 255 " after `--` are passed to the JITed program as argv. The driver\n" 256 " returns the entry's exit code, or 1 on a compile/link/lookup\n" 257 " error.\n" 258 "\n" 259 " Inputs are classified by suffix:\n" 260 " .c .cc .cpp C source\n" 261 " .wat .wasm WebAssembly source module\n" 262 " .o .obj object file\n" 263 " .a static archive\n" 264 " - read C source from stdin (single source only)\n" 265 "\n" 266 " Native JITed code may call any host symbol resolvable via " 267 "dlsym(RTLD_DEFAULT)\n" 268 " — typically libc. Wasm source inputs do not use this fallback.\n" 269 " The JIT path forces PIC; -fPIC / -fPIE / " 270 "-mcmodel\n" 271 " are accepted but have no observable effect.\n" 272 "\n" 273 "COMPILE OPTIONS\n" 274 " -O0 -O1 -O2 Optimization level (default -O0)\n" 275 " --no-jit Execute the entry through the IR interpreter\n" 276 " instead of JIT-compiled native code (forces " 277 "-O1\n" 278 " minimum so the optimizer IR is available)\n" 279 " -g Emit DWARF debug info\n" 280 " --time, --metrics Emit scoped compile/link/JIT timing to stderr\n" 281 " --bench-time Emit parseable compile/JIT/execution timings\n" 282 " -e SYMBOL Entry symbol (default `main`)\n" 283 " -target TRIPLE Cross-compile target (see `kit cc --help`)\n" 284 " --sysroot DIR Hosted libc sysroot for headers/defines with " 285 "-lc\n" 286 " -lc Enable hosted libc headers/defines; calls " 287 "resolve " 288 "via host dlsym\n" 289 " -fPIC -fpic Position-independent code (no-op for the JIT)\n" 290 " -fPIE -fpie Position-independent executable (no-op for the " 291 "JIT)\n" 292 " -mcmodel=MODEL small | medium | large (no-op for the JIT)\n" 293 " -Werror Treat warnings as errors\n" 294 " -fmax-errors=N Stop after N errors (0 = unlimited)\n" 295 "\n" 296 "PREPROCESSOR\n" 297 " -I DIR Add quoted-include search path\n" 298 " -isystem DIR Add system-include search path\n" 299 " -D NAME[=BODY] Define a macro\n" 300 " -U NAME Undefine a builtin/predefined macro\n" 301 "\n"))); 302 driver_printf( 303 "%.*s", 304 KIT_SLICE_ARG(KIT_SLICE_LIT( 305 "WASM SANDBOX\n" 306 " Wasm inputs run with a deny-by-default host-import policy. " 307 "No host\n" 308 " symbol or test-import fallback is enabled unless an explicit " 309 "flag\n" 310 " below requests it.\n" 311 " --wasm-memory-max=SIZE\n" 312 " Maximum total linear-memory reservation " 313 "(default 1G)\n" 314 " --wasm-instance-max=SIZE\n" 315 " Maximum runtime instance size (default 64M)\n" 316 " --wasm-memories-max=N\n" 317 " Maximum declared linear-memory count\n" 318 " --wasm-imports=deny|wasi|test\n" 319 " Import resolver policy (default deny). `test` " 320 "binds\n" 321 " only the in-tree env.host_add test shim.\n" 322 " `wasi` binds the configured WASI Preview1 " 323 "shim.\n" 324 " --wasm-wasi Alias for --wasm-imports=wasi\n" 325 " --wasm-env=none|allowlist|inherit\n" 326 " --wasm-env-pass=NAME, --wasm-env-set=NAME=VALUE\n" 327 " Guest environment policy for WASI env imports\n" 328 " --wasm-fs=none Filesystem disabled (default)\n" 329 " --wasm-map-dir=HOST=GUEST[:ro|rw]\n" 330 " --wasm-map-file=HOST=GUEST[:ro|rw]\n" 331 " --wasm-cwd=GUEST Guest cwd exposed through the WASI config\n" 332 " --wasm-stdio=null|inherit\n" 333 " --wasm-clock=none|monotonic|realtime\n" 334 " --wasm-random=none|host|seed:HEX\n" 335 " WASI host-resource policy; default is none\n" 336 "\n"))); 337 driver_printf( 338 "%.*s", 339 KIT_SLICE_ARG(KIT_SLICE_LIT( 340 "ARGV PASSTHROUGH\n" 341 " -- End of `kit run` options. Tokens after `--` " 342 "are\n" 343 " passed to the JITed program's main(argc, argv)\n" 344 " starting at argv[1]. argv[0] is synthesized " 345 "from\n" 346 " the first input (path, `<stdin>`, .o, or .a) " 347 "so\n" 348 " JITed code can index argv[0] like a hosted\n" 349 " program. Without `--` the program receives\n" 350 " argc==1 with argv[0] set and argv[1]==NULL.\n" 351 " --script FILE Run FILE as the sole source, passing every\n" 352 " later token to the program as argv (an " 353 "implicit\n" 354 " `--` after FILE). Implies -lc (hosted libc),\n" 355 " since scripts are usually hosted; on macOS " 356 "that\n" 357 " still needs --sysroot for header resolution.\n" 358 " Intended for `#!` script use:\n" 359 " #!/usr/bin/env -S kit run --script\n" 360 " Make the .c file executable and run it " 361 "directly;\n" 362 " the kernel appends the path + the user's args.\n" 363 " Add compile flags before --script, e.g.\n" 364 " #!/usr/bin/env -S kit run -g --script\n" 365 "\n" 366 "GETTING HELP\n" 367 " -h, --help Show this help and exit\n" 368 "\n" 369 "EXAMPLES\n" 370 " kit run hello.c\n" 371 " kit run -O2 -DNDEBUG main.c util.c\n" 372 " kit run main.c -- arg1 arg2\n" 373 " kit run --script script.c arg1 arg2 (as a #! interpreter)\n" 374 "\n" 375 "EXIT CODES\n" 376 " Returns the exit code of the JITed entry, or 1 on internal " 377 "failure.\n" 378 " 2 on bad command-line usage.\n"))); 379 } 380 381 static int run_alloc_arrays(RunOptions* o, int argc) { 382 size_t bound = (size_t)argc; 383 o->argv_bound = bound; 384 o->prog_argv = driver_alloc_zeroed(o->env, bound * sizeof(*o->prog_argv)); 385 if (!o->prog_argv) { 386 driver_errf(RUN_TOOL, "out of memory"); 387 return 1; 388 } 389 if (driver_inputs_init(&o->inputs, o->env, RUN_TOOL, argc) != 0) return 1; 390 if (driver_cflags_init( 391 &o->cf, o->env, 392 argc + DRIVER_HOSTED_MAX_INCLUDES + DRIVER_HOSTED_MAX_DEFINES) != 0) { 393 driver_errf(RUN_TOOL, "out of memory"); 394 return 1; 395 } 396 if (driver_target_features_init(&o->target_features, o->env, argc) != 0) { 397 driver_errf(RUN_TOOL, "out of memory"); 398 return 1; 399 } 400 if (driver_wasm_run_options_init(&o->wasm, o->env, bound) != 0) return 1; 401 return 0; 402 } 403 404 /* Decimal uint64 parse for -fmax-errors=N. Mirrors cc's helper. */ 405 static int run_parse_u64(const char* s, uint64_t* out) { 406 uint64_t v = 0; 407 int any = 0; 408 if (!s) return 1; 409 while (*s) { 410 unsigned d; 411 if (*s < '0' || *s > '9') return 1; 412 d = (unsigned)(*s - '0'); 413 if (v > (UINT64_MAX - d) / 10u) return 1; 414 v = v * 10u + d; 415 any = 1; 416 s++; 417 } 418 if (!any) return 1; 419 *out = v; 420 return 0; 421 } 422 423 static int run_record_mcmodel(RunOptions* o, const char* val) { 424 if (driver_streq(val, "small")) { 425 o->target.code_model = KIT_CM_SMALL; 426 return 0; 427 } 428 if (driver_streq(val, "medium")) { 429 o->target.code_model = KIT_CM_MEDIUM; 430 return 0; 431 } 432 if (driver_streq(val, "large")) { 433 o->target.code_model = KIT_CM_LARGE; 434 return 0; 435 } 436 if (driver_streq(val, "medlow")) { 437 o->target.code_model = KIT_CM_SMALL; 438 return 0; 439 } 440 if (driver_streq(val, "medany")) { 441 o->target.code_model = KIT_CM_MEDIUM; 442 return 0; 443 } 444 driver_errf(RUN_TOOL, "unknown -mcmodel value: %.*s", 445 KIT_SLICE_ARG(kit_slice_cstr(val))); 446 return 1; 447 } 448 449 static int run_classify_positional(RunOptions* o, const char* a) { 450 int r = driver_inputs_classify(&o->inputs, a); 451 if (r < 0) return 1; 452 if (r == 0) { 453 driver_errf(RUN_TOOL, "input does not have a recognized suffix: %.*s", 454 KIT_SLICE_ARG(kit_slice_cstr(a))); 455 return 1; 456 } 457 return 0; 458 } 459 460 static int run_path_is_wasm_source(const char* path) { 461 return driver_has_suffix(path, ".wat") || driver_has_suffix(path, ".wasm"); 462 } 463 464 /* Scan raw wasm bytes for a custom section named `want`. 465 * On success sets *len_out and returns a pointer into `p`; returns NULL on 466 * miss or malformed input. The returned pointer is valid only while `p` is. */ 467 static const char* wasm_scan_custom(const uint8_t* p, size_t total, 468 const char* want, uint32_t* len_out) { 469 const uint8_t* end = p + total; 470 uint8_t id; 471 uint32_t size, shift; 472 const uint8_t* sec_end; 473 if (total < 8) return NULL; 474 p += 8; /* skip magic + version */ 475 while (p < end) { 476 uint8_t b; 477 id = *p++; 478 size = 0; shift = 0; 479 for (;;) { 480 if (p >= end) return NULL; 481 b = *p++; 482 size |= (uint32_t)(b & 0x7f) << shift; 483 if (!(b & 0x80)) break; 484 shift += 7; 485 if (shift > 28) return NULL; 486 } 487 if ((uint32_t)(end - p) < size) return NULL; 488 sec_end = p + size; 489 if (id == 0) { 490 uint32_t nlen = 0; 491 shift = 0; 492 for (;;) { 493 if (p >= sec_end) { p = sec_end; goto next_section; } 494 b = *p++; 495 nlen |= (uint32_t)(b & 0x7f) << shift; 496 if (!(b & 0x80)) break; 497 shift += 7; 498 if (shift > 28) { p = sec_end; goto next_section; } 499 } 500 if ((uint32_t)(sec_end - p) >= nlen) { 501 size_t wlen = driver_strlen(want); 502 if (nlen == (uint32_t)wlen && memcmp(p, want, wlen) == 0) { 503 p += nlen; 504 *len_out = (uint32_t)(sec_end - p); 505 return (const char*)p; 506 } 507 } 508 } 509 next_section: 510 p = sec_end; 511 } 512 return NULL; 513 } 514 515 static int run_inputs_have_wasm_source(const DriverInputs* in) { 516 uint32_t i; 517 for (i = 0; i < in->nsources; ++i) 518 if (run_path_is_wasm_source(in->sources[i])) return 1; 519 return 0; 520 } 521 522 static int run_inputs_have_non_wasm(const DriverInputs* in) { 523 uint32_t i; 524 if (in->nsource_memory || in->nobject_files || in->narchives) return 1; 525 for (i = 0; i < in->nsources; ++i) 526 if (!run_path_is_wasm_source(in->sources[i])) return 1; 527 return 0; 528 } 529 530 static int run_apply_hosted_profile(RunOptions* o) { 531 DriverHostedRequest req; 532 uint32_t i; 533 if (!o->wants_hosted_libc) return 0; 534 { 535 DriverHostedRequest z = {0}; 536 req = z; 537 } 538 req.env = o->env; 539 req.tool = RUN_TOOL; 540 req.target = o->target; 541 req.sysroot = o->sysroot; 542 req.static_link = 0; 543 req.link_inputs = 0; 544 if (driver_hosted_resolve(&req, &o->hosted) != 0) return 1; 545 for (i = 0; i < o->hosted.nsystem_includes; ++i) { 546 o->cf.system_include_dirs[o->cf.nsystem_include_dirs++] = 547 o->hosted.system_includes[i]; 548 } 549 for (i = 0; i < o->hosted.ndefines; ++i) { 550 o->cf.defines[o->cf.ndefines++] = o->hosted.defines[i]; 551 } 552 return 0; 553 } 554 555 static int run_parse(int argc, char** argv, RunOptions* o) { 556 int i; 557 int after_dash_dash = 0; 558 if (run_alloc_arrays(o, argc) != 0) return 1; 559 o->target = driver_host_target(); 560 561 /* Reserve argv[0] for a synthetic program name filled in below. User 562 * args after `--` start at argv[1]. */ 563 o->prog_argc = 1; 564 565 for (i = 1; i < argc; ++i) { 566 const char* a = argv[i]; 567 568 if (after_dash_dash) { 569 o->prog_argv[o->prog_argc++] = argv[i]; 570 continue; 571 } 572 if (driver_streq(a, "--")) { 573 after_dash_dash = 1; 574 continue; 575 } 576 /* `--script FILE`: shebang entry point. The kernel's `#!` mechanism 577 * appends the script path and the user's arguments after our flags, with 578 * no way to inject a `--` between them. `--script` names the sole source 579 * file (the next argv element, supplied by the kernel) and routes every 580 * later token — flag-shaped or not — to the program's argv, exactly like 581 * an implicit `--` after the script. See driver_help_run / DRIVER.md. */ 582 if (driver_streq(a, "--script")) { 583 if (++i >= argc) { 584 driver_errf(RUN_TOOL, "--script requires a source-file argument"); 585 return 1; 586 } 587 /* Scripts are overwhelmingly hosted programs, so default `--script` to 588 * hosted libc — under the JIT that only enables libc headers/macros 589 * (symbols resolve at run time via host dlsym), so the only added cost 590 * is needing a sysroot for #include resolution. An earlier explicit 591 * -lc is a harmless no-op; this just spares every shebang line from 592 * repeating it. */ 593 o->wants_hosted_libc = 1; 594 if (run_classify_positional(o, argv[i]) != 0) return 1; 595 after_dash_dash = 1; 596 continue; 597 } 598 599 { 600 int wr = driver_wasm_run_try_consume(&o->wasm, RUN_TOOL, argc, argv, &i); 601 if (wr < 0) return 1; 602 if (wr > 0) continue; 603 } 604 605 { 606 int r = 607 driver_cflags_try_consume(&o->cf, o->env, RUN_TOOL, argc, argv, &i); 608 if (r < 0) return 1; 609 if (r > 0) continue; 610 } 611 612 if (driver_streq(a, "-g")) { 613 o->debug_info = 1; 614 continue; 615 } 616 if (driver_streq(a, "--bench-time")) { 617 o->bench_time = 1; 618 continue; 619 } 620 if (driver_streq(a, "--time") || driver_streq(a, "--metrics")) { 621 o->metrics = 1; 622 continue; 623 } 624 if (driver_streq(a, "--no-jit")) { 625 o->no_jit = 1; 626 continue; 627 } 628 if (driver_streq(a, "-O0")) { 629 o->opt_level = 0; 630 continue; 631 } 632 if (driver_streq(a, "-O1")) { 633 o->opt_level = 1; 634 continue; 635 } 636 if (driver_streq(a, "-O2")) { 637 o->opt_level = 2; 638 continue; 639 } 640 641 if (driver_streq(a, "-Werror")) { 642 o->warnings_are_errors = 1; 643 continue; 644 } 645 if (driver_strneq(a, "-fmax-errors=", 13)) { 646 uint64_t v; 647 if (run_parse_u64(a + 13, &v) != 0 || v > 0xFFFFFFFFu) { 648 driver_errf(RUN_TOOL, "-fmax-errors= requires a non-negative integer"); 649 return 1; 650 } 651 o->max_errors = (uint32_t)v; 652 continue; 653 } 654 655 if (driver_streq(a, "-fPIC")) { 656 o->target.pic = KIT_PIC_PIC; 657 continue; 658 } 659 if (driver_streq(a, "-fpic")) { 660 o->target.pic = KIT_PIC_PIC; 661 continue; 662 } 663 if (driver_streq(a, "-fPIE")) { 664 o->target.pic = KIT_PIC_PIE; 665 continue; 666 } 667 if (driver_streq(a, "-fpie")) { 668 o->target.pic = KIT_PIC_PIE; 669 continue; 670 } 671 672 if (driver_strneq(a, "-mcmodel=", 9)) { 673 if (run_record_mcmodel(o, a + 9) != 0) return 1; 674 continue; 675 } 676 { 677 int tr = driver_target_features_try_consume(&o->target_features, o->env, 678 RUN_TOOL, argc, argv, &i); 679 if (tr < 0) return 1; 680 if (tr > 0) continue; 681 } 682 683 if (driver_streq(a, "-target")) { 684 if (++i >= argc) { 685 driver_errf(RUN_TOOL, "-target requires an argument"); 686 return 1; 687 } 688 if (driver_target_from_triple(argv[i], &o->target) != 0) { 689 driver_errf(RUN_TOOL, "unrecognized target triple: %.*s", 690 KIT_SLICE_ARG(kit_slice_cstr(argv[i]))); 691 return 1; 692 } 693 continue; 694 } 695 if (driver_streq(a, "--sysroot") || driver_streq(a, "-isysroot")) { 696 if (++i >= argc) { 697 driver_errf(RUN_TOOL, "%.*s requires an argument", 698 KIT_SLICE_ARG(kit_slice_cstr(a))); 699 return 1; 700 } 701 o->sysroot = argv[i]; 702 continue; 703 } 704 if (driver_strneq(a, "--sysroot=", 10)) { 705 o->sysroot = a + 10; 706 continue; 707 } 708 if (driver_streq(a, "-lc")) { 709 o->wants_hosted_libc = 1; 710 continue; 711 } 712 if (driver_streq(a, "-l")) { 713 if (++i >= argc) { 714 driver_errf(RUN_TOOL, "-l requires an argument"); 715 return 1; 716 } 717 if (!driver_streq(argv[i], "c")) { 718 driver_errf(RUN_TOOL, "unsupported hosted library for JIT: -l%.*s", 719 KIT_SLICE_ARG(kit_slice_cstr(argv[i]))); 720 return 1; 721 } 722 o->wants_hosted_libc = 1; 723 continue; 724 } 725 if (driver_strneq(a, "-l", 2)) { 726 if (!driver_streq(a + 2, "c")) { 727 driver_errf(RUN_TOOL, "unsupported hosted library for JIT: %.*s", 728 KIT_SLICE_ARG(kit_slice_cstr(a))); 729 return 1; 730 } 731 o->wants_hosted_libc = 1; 732 continue; 733 } 734 735 if (driver_streq(a, "-e")) { 736 if (++i >= argc) { 737 driver_errf(RUN_TOOL, "-e requires an argument"); 738 return 1; 739 } 740 o->entry = argv[i]; 741 continue; 742 } 743 744 if (driver_streq(a, "-")) { 745 if (run_classify_positional(o, a) != 0) return 1; 746 continue; 747 } 748 if (a[0] == '-' && a[1] != '\0') { 749 driver_errf(RUN_TOOL, "unknown flag: %.*s", 750 KIT_SLICE_ARG(kit_slice_cstr(a))); 751 return 1; 752 } 753 754 if (run_classify_positional(o, a) != 0) return 1; 755 } 756 757 if (driver_inputs_count(&o->inputs) == 0) { 758 driver_errf(RUN_TOOL, "no input files"); 759 run_usage(); 760 return 1; 761 } 762 if (run_inputs_have_wasm_source(&o->inputs) && 763 run_inputs_have_non_wasm(&o->inputs)) { 764 driver_errf(RUN_TOOL, 765 "sandboxed wasm run does not support mixed native inputs"); 766 return 1; 767 } 768 if (!o->entry && run_inputs_have_wasm_source(&o->inputs) && 769 !run_inputs_have_non_wasm(&o->inputs) && 770 o->inputs.nsources == 1) { 771 DriverLoad lf = {0}; 772 KitSlice bytes = {0}; 773 if (driver_load_bytes(&o->env->file_io, RUN_TOOL, 774 o->inputs.sources[0], &lf, &bytes) == 0) { 775 uint32_t elen = 0; 776 const char* ep = wasm_scan_custom((const uint8_t*)bytes.s, 777 (size_t)bytes.len, 778 "kit-entry", &elen); 779 if (ep && elen > 0 && elen < 256) { 780 char* buf = (char*)o->env->heap->alloc(o->env->heap, elen + 1u, 1); 781 if (buf) { 782 memcpy(buf, ep, elen); 783 buf[elen] = '\0'; 784 o->entry = buf; 785 } 786 } 787 driver_release_bytes(&o->env->file_io, &lf); 788 } 789 } 790 if (!o->entry) o->entry = "main"; 791 if (run_apply_hosted_profile(o) != 0) return 1; 792 793 /* Synthetic argv[0]. Hosted programs conventionally read argv[0] as 794 * the program name; under `kit run` there is no executable path, so 795 * use the first input's display name. */ 796 o->prog_argv[0] = (char*)driver_inputs_first_name(&o->inputs); 797 o->wasm.args = (const char* const*)o->prog_argv; 798 o->wasm.nargs = o->prog_argc; 799 return 0; 800 } 801 802 static void run_options_release(RunOptions* o) { 803 size_t bound = o->argv_bound; 804 driver_hosted_plan_fini(o->env, &o->hosted); 805 driver_wasm_run_options_fini(&o->wasm); 806 driver_inputs_release(&o->inputs); 807 driver_target_features_fini(&o->target_features, o->env); 808 driver_cflags_fini(&o->cf, o->env); 809 driver_free(o->env, o->prog_argv, bound * sizeof(*o->prog_argv)); 810 } 811 812 static void run_fill_compile_opts(const RunOptions* o, 813 KitCCompileOptions* copts) { 814 KitCCompileOptions z = {0}; 815 *copts = z; 816 /* The interpreter consumes the O1 PReg-path IR; force at least -O1 so the 817 * optimizer runs and each function is captured into the InterpProgram. */ 818 copts->code.opt_level = (o->no_jit && o->opt_level < 1) ? 1 : o->opt_level; 819 copts->code.debug_info = o->debug_info; 820 copts->diagnostics.warnings_are_errors = o->warnings_are_errors; 821 copts->diagnostics.max_errors = o->max_errors; 822 } 823 824 /* Compile every C source through the caller-owned compiler, load .o/.a 825 * inputs, and JIT-link. On success *out_jit owns the JIT image; caller 826 * releases via kit_jit_free. The compiler must outlive the JIT — it 827 * backs jit->c, which kit_jit_lookup dereferences. */ 828 static int run_compile_and_jit(RunOptions* o, KitCompiler* compiler, 829 const KitJitHost* host, KitJit** out_jit) { 830 KitCCompileOptions copts; 831 KitPreprocessOptions pp; 832 void* (*extern_resolver)(void*, KitSlice) = driver_dlsym_resolver; 833 run_fill_compile_opts(o, &copts); 834 driver_cflags_fill_pp(&o->cf, &pp); 835 if (run_inputs_have_wasm_source(&o->inputs) || 836 driver_wasm_run_options_used(&o->wasm)) 837 extern_resolver = NULL; 838 return driver_inputs_compile_and_jit(&o->inputs, compiler, host, &copts, &pp, 839 o->entry, extern_resolver, NULL, 840 out_jit); 841 } 842 843 typedef int (*MainFn)(int, char**); 844 845 /* --- crash backtrace ------------------------------------------------------ 846 * When the JITed program faults, driver_run_with_crash_guard captures the 847 * return-address chain (innermost first) inside its fault handler and hands it 848 * here; we open the image's DWARF (best effort) and symbolize each frame to 849 * stderr. */ 850 851 typedef struct RunCrashCtx { 852 DriverEnv* env; 853 KitJit* jit; 854 int signo; /* filled in run_on_crash so the caller can derive the exit code */ 855 } RunCrashCtx; 856 857 static void run_bt_emit(void* user, const char* line) { 858 (void)user; 859 driver_logf("%s", line); /* driver_logf appends the newline */ 860 } 861 862 static void run_on_crash(void* user, int signo, const uint64_t* pcs, int npcs) { 863 RunCrashCtx* c = (RunCrashCtx*)user; 864 KitContext ctx = driver_env_to_context(c->env); 865 KitDebugInfo* dwarf = NULL; 866 DriverBtCtx btc; 867 868 c->signo = signo; 869 driver_logf("kit: program received signal %d; backtrace:", signo); 870 871 /* DWARF is best effort: without it we still print addresses + JIT symbols. */ 872 if (c->jit) { 873 const KitObjFile* view = kit_jit_view(c->jit); 874 if (view) (void)kit_dwarf_open(&ctx, view, &dwarf); 875 } 876 877 memset(&btc, 0, sizeof btc); 878 btc.jit = c->jit; 879 btc.dwarf = dwarf; 880 btc.emit = run_bt_emit; 881 btc.emit_user = NULL; 882 driver_backtrace_print_pcs(&btc, pcs, npcs); 883 884 if (dwarf) kit_dwarf_free(dwarf); 885 } 886 887 /* Host-identity symbol resolver for the interpreter. 888 * 889 * The interpreter holds symbol names as they appear in the object/image symbol 890 * table (already target-mangled, e.g. a leading `_` on Mach-O). kit_jit_lookup 891 * only finds GLOBAL-bind symbols, but the toy/C frontends emit module-private 892 * data and helper functions with LOCAL bind. So resolve by iterating the JIT 893 * image's full symbol table (locals included) for an exact name match — this 894 * also avoids the re-mangling kit_jit_lookup would apply. Extern/libc symbols 895 * not defined in the image fall back to host dlsym (which wants the unmangled 896 * name, so try with a leading `_` stripped too). */ 897 static void* interp_jit_resolve(void* ctx, KitSlice name) { 898 KitJit* jit = (KitJit*)ctx; 899 void* p = NULL; 900 /* The interpreter holds object-table names (target-mangled, e.g. a leading 901 * `_` on Mach-O); the JIT image exposes the canonical unmangled names. Match 902 * tolerating a single leading-underscore on either side. */ 903 KitSlice alt = name; 904 if (name.len > 1 && name.s[0] == '_') { 905 alt.s = name.s + 1; 906 alt.len = name.len - 1; 907 } 908 /* Two passes so an EXACT name match always wins over the underscore-stripped 909 * fallback (avoids a local `_g` masking a global `g`, or vice-versa). */ 910 int pass; 911 for (pass = 0; pass < 2 && !p; ++pass) { 912 KitSlice want = (pass == 0) ? name : alt; 913 KitJitSymIter* it = NULL; 914 if (pass == 1 && alt.s == name.s) break; /* no distinct stripped form */ 915 if (!jit || kit_jit_sym_iter_new(jit, &it) != KIT_OK) break; 916 { 917 KitJitSym s; 918 while (kit_jit_sym_iter_next(it, &s) == KIT_ITER_ITEM) { 919 size_t k; 920 if (s.name.len != want.len) continue; 921 for (k = 0; k < want.len && s.name.s[k] == want.s[k]; ++k) { 922 } 923 if (k == want.len) { 924 p = (void*)(uintptr_t)s.addr; 925 break; 926 } 927 } 928 kit_jit_sym_iter_free(it); 929 } 930 } 931 if (!p) { 932 p = driver_dlsym_resolver(NULL, name); 933 if (!p && name.s[0] == '_') p = driver_dlsym_resolver(NULL, alt); 934 } 935 return p; 936 } 937 938 /* Thread-local resolver for the interpreter. A thread-local symbol resolves to 939 * a Mach-O TLV descriptor (whose +16 slot holds the storage) or, on ELF/COFF, 940 * directly to the in-image storage; kit_jit_tls_addr normalizes both to the 941 * variable's single in-image instance and returns NULL for anything it can't 942 * safely resolve (a foreign/extern thread-local resolved through the host). We 943 * return NULL in those cases so the engine diagnoses cleanly rather than 944 * treating a foreign pointer as the variable's storage. */ 945 static void* interp_jit_resolve_tls(void* ctx, KitSlice name, int64_t addend) { 946 KitJit* jit = (KitJit*)ctx; 947 void* sym = interp_jit_resolve(ctx, name); 948 void* tls; 949 if (!sym) return NULL; 950 tls = kit_jit_tls_addr(jit, sym); 951 return tls ? (uint8_t*)tls + addend : NULL; 952 } 953 954 int driver_run(int argc, char** argv) { 955 DriverEnv env; 956 RunOptions ro = {0}; 957 KitContext ctx; 958 KitJitHost jhost; 959 KitTarget* target = NULL; 960 KitCompiler* compiler = NULL; 961 KitJit* jit = NULL; 962 KitInterpProgram* interp = NULL; 963 RunMetrics metrics_storage; 964 RunMetrics* metrics = NULL; 965 void* sym; 966 MainFn entry_fn; 967 int rc; 968 uint64_t bench_total_start = 0; 969 uint64_t bench_compile_start = 0; 970 uint64_t bench_compile_end = 0; 971 uint64_t bench_exec_start = 0; 972 uint64_t bench_exec_end = 0; 973 974 if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { 975 driver_help_run(); 976 return 0; 977 } 978 979 driver_env_init(&env); 980 ro.env = &env; 981 982 if (run_parse(argc, argv, &ro) != 0) { 983 run_options_release(&ro); 984 driver_env_fini(&env); 985 return 2; 986 } 987 988 if (ro.metrics || ro.bench_time) { 989 run_metrics_init(&metrics_storage, &env, ro.bench_time); 990 metrics = &metrics_storage; 991 env.metrics = &metrics->iface; 992 if (ro.metrics && !ro.bench_time) driver_logf("kit metrics:"); 993 run_metrics_begin(metrics, "run.total"); 994 } 995 if (ro.bench_time) bench_total_start = driver_now_ns(); 996 997 /* Compiler backs the JIT image — keep it alive across kit_jit_lookup 998 * and the entry call, free after kit_jit_free. */ 999 ctx = driver_env_to_context(&env); 1000 jhost = driver_env_to_jit_host(&env); 1001 if (driver_target_new(&ctx, ro.target, &ro.target_features, RUN_TOOL, 1002 &target) != KIT_OK || 1003 driver_compiler_new(target, &ctx, &compiler) != KIT_OK) { 1004 driver_errf(RUN_TOOL, "failed to initialize compiler"); 1005 kit_target_free(target); 1006 run_metrics_finish(metrics); 1007 run_options_release(&ro); 1008 driver_env_fini(&env); 1009 return 1; 1010 } 1011 1012 /* For --no-jit, attach an InterpProgram so the optimizer captures each 1013 * function's IR as it compiles. The native object/JIT image is still built 1014 * (it lays out data globals and resolves externs/function pointers); only 1015 * the entry's *execution* is routed through the interpreter. */ 1016 if (ro.no_jit) { 1017 interp = kit_interp_program_new(compiler); 1018 if (!interp) { 1019 driver_errf(RUN_TOOL, "failed to initialize interpreter"); 1020 driver_compiler_free(compiler); 1021 kit_target_free(target); 1022 run_metrics_finish(metrics); 1023 run_options_release(&ro); 1024 driver_env_fini(&env); 1025 return 1; 1026 } 1027 kit_interp_program_attach(interp, compiler); 1028 } 1029 1030 if (ro.bench_time) bench_compile_start = driver_now_ns(); 1031 run_metrics_begin(metrics, "run.compile_and_jit"); 1032 rc = run_compile_and_jit(&ro, compiler, &jhost, &jit); 1033 run_metrics_end(metrics, "run.compile_and_jit"); 1034 if (ro.bench_time) bench_compile_end = driver_now_ns(); 1035 if (rc != 0) { 1036 if (ro.bench_time) 1037 run_bench_time("compile_and_jit", 1038 bench_compile_end - bench_compile_start); 1039 kit_interp_program_free(interp); 1040 driver_compiler_free(compiler); 1041 kit_target_free(target); 1042 run_metrics_finish(metrics); 1043 run_options_release(&ro); 1044 driver_env_fini(&env); 1045 return rc; 1046 } 1047 1048 run_metrics_begin(metrics, "run.jit_lookup"); 1049 sym = kit_jit_lookup(jit, kit_slice_cstr(ro.entry)); 1050 run_metrics_end(metrics, "run.jit_lookup"); 1051 if (!sym) { 1052 driver_errf(RUN_TOOL, "entry symbol not found: %.*s", 1053 KIT_SLICE_ARG(kit_slice_cstr(ro.entry))); 1054 kit_interp_program_free(interp); 1055 kit_jit_free(jit); 1056 driver_compiler_free(compiler); 1057 kit_target_free(target); 1058 run_metrics_finish(metrics); 1059 run_options_release(&ro); 1060 driver_env_fini(&env); 1061 return 1; 1062 } 1063 1064 { 1065 union { 1066 void* p; 1067 MainFn fn; 1068 } u; 1069 u.p = sym; 1070 entry_fn = u.fn; 1071 } 1072 1073 /* --no-jit: execute the entry through the IR interpreter. There is NO JIT 1074 * fallback — if the entry was not captured as interpretable IR (e.g. it came 1075 * from a precompiled .o, or uses an unsupported construct), that is an error. 1076 * The native object/JIT image is still built, but only to lay out data 1077 * globals and resolve externs/function pointers for the interpreter; the 1078 * entry's code is never run as native. */ 1079 if (ro.no_jit) { 1080 KitInterpHost host; 1081 KitInterpFunc* ifn; 1082 int64_t ret = 0; 1083 KitInterpStatus s; 1084 host.translate = NULL; /* host-identity: abstract addrs are host pointers */ 1085 host.resolve_sym = interp_jit_resolve; 1086 host.resolve_tls = interp_jit_resolve_tls; 1087 host.ctx = jit; 1088 kit_interp_program_set_host(interp, &host); 1089 /* Wasm modules need their instance/linear-memory set up and a 2-call 1090 * (init, entry) sequence with the instance pointer as the argument. */ 1091 if (driver_wasm_run_call_entry_interp(&ro.wasm, RUN_TOOL, compiler, jit, 1092 interp, ro.entry, &rc)) 1093 goto after_entry; 1094 if (driver_wasm_run_options_used(&ro.wasm)) { 1095 driver_errf(RUN_TOOL, "wasm sandbox flags require a wasm input"); 1096 rc = 1; 1097 goto after_entry; 1098 } 1099 ifn = kit_interp_lookup(interp, kit_slice_cstr(ro.entry)); 1100 if (!ifn) { 1101 driver_errf(RUN_TOOL, 1102 "interp: entry %.*s has no interpretable IR (--no-jit " 1103 "requires an IR-compiled entry; .o/.a inputs are not " 1104 "supported)", 1105 KIT_SLICE_ARG(kit_slice_cstr(ro.entry))); 1106 rc = 1; 1107 goto after_entry; 1108 } 1109 run_metrics_begin(metrics, "run.entry_call"); 1110 if (ro.bench_time) bench_exec_start = driver_now_ns(); 1111 s = kit_interp_call(interp, ifn, (int)ro.prog_argc, ro.prog_argv, &ret); 1112 if (ro.bench_time) bench_exec_end = driver_now_ns(); 1113 run_metrics_end(metrics, "run.entry_call"); 1114 if (s == KIT_INTERP_DONE) { 1115 rc = (int)ret; 1116 } else { 1117 /* The engine already emitted an "interp: ... not supported" / trap 1118 * diagnostic; surface a nonzero status (no native fallback). */ 1119 driver_errf(RUN_TOOL, "interp: could not execute %.*s", 1120 KIT_SLICE_ARG(kit_slice_cstr(ro.entry))); 1121 rc = 1; 1122 } 1123 goto after_entry; 1124 } 1125 1126 run_metrics_begin(metrics, "run.entry_call"); 1127 if (ro.bench_time) bench_exec_start = driver_now_ns(); 1128 if (!driver_wasm_run_call_entry(&ro.wasm, RUN_TOOL, compiler, jit, sym, 1129 &rc)) { 1130 if (driver_wasm_run_options_used(&ro.wasm)) { 1131 driver_errf(RUN_TOOL, "wasm sandbox flags require a wasm input"); 1132 rc = 1; 1133 } else { 1134 RunCrashCtx cc; 1135 cc.env = &env; 1136 cc.jit = jit; 1137 cc.signo = 0; 1138 if (driver_run_with_crash_guard(&env, ro.target.arch, entry_fn, 1139 (int)ro.prog_argc, ro.prog_argv, &rc, 1140 run_on_crash, &cc)) 1141 rc = 128 + cc.signo; /* program faulted; shell convention */ 1142 } 1143 } 1144 if (ro.bench_time) bench_exec_end = driver_now_ns(); 1145 run_metrics_end(metrics, "run.entry_call"); 1146 after_entry: 1147 if (ro.bench_time) { 1148 run_bench_time("compile_and_jit", bench_compile_end - bench_compile_start); 1149 run_bench_time("execution", bench_exec_end - bench_exec_start); 1150 run_bench_time("total", bench_exec_end - bench_total_start); 1151 } 1152 1153 kit_interp_program_free(interp); 1154 kit_jit_free(jit); 1155 driver_compiler_free(compiler); 1156 kit_target_free(target); 1157 run_metrics_finish(metrics); 1158 run_options_release(&ro); 1159 driver_env_fini(&env); 1160 return rc; 1161 }