kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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 }