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