kit

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

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); }