kit

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

common.c (24004B)


      1 /* Pure libc bits with no OS-specific behavior: the heap vtable, the
      2  * stderr diag sink, stdout/fd writers, and the small printf/errf/alloc
      3  * helpers that route through stdio + malloc. Compiled on every host. */
      4 
      5 #include <kit/profile.h>
      6 #include <stdarg.h>
      7 #include <stdint.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 
     12 #include "env_internal.h"
     13 
     14 /* ---------------- heap (libc-backed) ---------------- */
     15 
     16 /* Heap allocator metrics, reusing the KitProfiler counter machinery. The heap
     17  * is the process-wide allocation chokepoint (arenas and pools all bottom out
     18  * here), so it counts into the profiler pointed to by KitHeap.user — driver_env
     19  * wires that to a profiler when KIT_METRICS is set, leaving it NULL (one
     20  * branch, no work) otherwise. Counters live in the embedder-owned external id
     21  * range so the kit-core counter enum stays untouched; names are attached once
     22  * via kit_profiler_define_counter and read back by the generic counter dump.
     23  * The ~32 KiB threshold approximates an arena block (default block is 64 KiB),
     24  * the dominant large-allocation source. */
     25 enum {
     26   HEAP_C_ALLOCS = KIT_PROFILE_COUNTER_EXTERNAL_FIRST,
     27   HEAP_C_ALLOC_KIB,
     28   HEAP_C_LARGE_ALLOCS, /* >= 32 KiB, ~arena blocks */
     29   HEAP_C_REALLOCS,     /* grows (the only realloc kit issues) */
     30   HEAP_C_REALLOC_MOVES,
     31   HEAP_C_FREES,
     32 };
     33 
     34 static void driver_heap_metrics_define(KitProfiler* pr) {
     35   if (!pr) return;
     36   kit_profiler_define_counter(pr, (KitProfileCounter)HEAP_C_ALLOCS,
     37                               "heap.allocs");
     38   kit_profiler_define_counter(pr, (KitProfileCounter)HEAP_C_ALLOC_KIB,
     39                               "heap.alloc_kib");
     40   kit_profiler_define_counter(pr, (KitProfileCounter)HEAP_C_LARGE_ALLOCS,
     41                               "heap.large_allocs");
     42   kit_profiler_define_counter(pr, (KitProfileCounter)HEAP_C_REALLOCS,
     43                               "heap.reallocs");
     44   kit_profiler_define_counter(pr, (KitProfileCounter)HEAP_C_REALLOC_MOVES,
     45                               "heap.realloc_moves");
     46   kit_profiler_define_counter(pr, (KitProfileCounter)HEAP_C_FREES,
     47                               "heap.frees");
     48 }
     49 
     50 /* Process-wide compile metrics, opt-in via KIT_METRICS. One KitProfiler backs
     51  * both the heap-allocator counters (wired through KitHeap.user, below) and the
     52  * libkit scope timers/counters (wired through DriverEnv.profiler ->
     53  * KitContext.profiler, so the metrics_scope and metrics_count calls the
     54  * optimizer, linker, and JIT already emit accumulate here). The heap is global,
     55  * so its
     56  * stats are too; this stays NULL/inert unless KIT_METRICS asks for it. */
     57 static KitProfiler* g_metrics_prof;
     58 static int g_metrics_inited;
     59 
     60 static void driver_metrics_dump(void) {
     61   KitProfiler* pr = g_metrics_prof;
     62   uint32_t id;
     63   if (!pr) return;
     64   fprintf(stderr, "kit metrics:\n");
     65   /* Scope timers first (raw host cycle-counter ticks + call count), in id order
     66    * so the compile pipeline reads top-to-bottom. Names resolve through the
     67    * kit-core scope table; unnamed scopes are skipped silently. */
     68   for (id = 1; id < KIT_PROFILE_ID_COUNT; ++id) {
     69     uint64_t count = kit_profiler_scope_count(pr, (KitProfileScope)id);
     70     uint64_t ticks = kit_profiler_scope_ticks(pr, (KitProfileScope)id);
     71     const char* name;
     72     if (!count) continue;
     73     name = kit_profiler_scope_name(pr, (KitProfileScope)id);
     74     if (!name) continue;
     75     fprintf(stderr, "  %s %llu ticks (%llu calls)\n", name,
     76             (unsigned long long)ticks, (unsigned long long)count);
     77   }
     78   /* Then every non-zero counter (heap.* in the external range, opt.* and link.*
     79    * in the kit range). */
     80   for (id = 1; id < KIT_PROFILE_ID_COUNT; ++id) {
     81     uint64_t value = kit_profiler_counter_value(pr, (KitProfileCounter)id);
     82     const char* name;
     83     if (!value) continue;
     84     name = kit_profiler_counter_name(pr, (KitProfileCounter)id);
     85     if (!name) continue;
     86     fprintf(stderr, "  %s=%llu\n", name, (unsigned long long)value);
     87   }
     88 }
     89 
     90 KitProfiler* driver_metrics_profiler(void) {
     91   const char* e;
     92   if (g_metrics_inited) return g_metrics_prof;
     93   g_metrics_inited = 1;
     94   e = getenv("KIT_METRICS");
     95   if (!(e && e[0] && e[0] != '0')) return NULL;
     96   /* Raw libc alloc (not through the vtable) so the profiler storage itself is
     97    * not counted and there is no reentrancy. */
     98   g_metrics_prof = (KitProfiler*)calloc(1, sizeof(*g_metrics_prof));
     99   if (!g_metrics_prof) return NULL;
    100   driver_heap_metrics_define(g_metrics_prof);
    101   /* Wire the heap counter sink unless something already claimed it. */
    102   if (!g_heap_libc.user) g_heap_libc.user = g_metrics_prof;
    103   atexit(driver_metrics_dump);
    104   return g_metrics_prof;
    105 }
    106 
    107 static void* heap_libc_alloc(KitHeap* h, size_t size, size_t align) {
    108   KitProfiler* pr;
    109   (void)align; /* malloc satisfies all max_align_t alignments */
    110   /* Lazily arm metrics for any allocation that precedes driver_env_init (the
    111    * usual arming site); idempotent and a single branch once inited. */
    112   if (!g_metrics_inited) (void)driver_metrics_profiler();
    113   pr = h ? (KitProfiler*)h->user : NULL;
    114   if (pr && size) {
    115     kit_profiler_count(pr, (KitProfileCounter)HEAP_C_ALLOCS, 1);
    116     kit_profiler_count(pr, (KitProfileCounter)HEAP_C_ALLOC_KIB, size >> 10);
    117     if (size >= 32u * 1024u)
    118       kit_profiler_count(pr, (KitProfileCounter)HEAP_C_LARGE_ALLOCS, 1);
    119   }
    120   return size ? malloc(size) : NULL;
    121 }
    122 
    123 static void* heap_libc_realloc(KitHeap* h, void* p, size_t old_size,
    124                                size_t new_size, size_t align) {
    125   KitProfiler* pr = h ? (KitProfiler*)h->user : NULL;
    126   void* np;
    127   (void)old_size;
    128   (void)align;
    129   if (pr) kit_profiler_count(pr, (KitProfileCounter)HEAP_C_REALLOCS, 1);
    130   np = realloc(p, new_size);
    131   if (pr && p && np && np != p)
    132     kit_profiler_count(pr, (KitProfileCounter)HEAP_C_REALLOC_MOVES, 1);
    133   return np;
    134 }
    135 
    136 static void heap_libc_free(KitHeap* h, void* p, size_t size) {
    137   KitProfiler* pr = h ? (KitProfiler*)h->user : NULL;
    138   (void)size;
    139   if (pr && p) kit_profiler_count(pr, (KitProfileCounter)HEAP_C_FREES, 1);
    140   free(p);
    141 }
    142 
    143 KitHeap g_heap_libc = {
    144     heap_libc_alloc,
    145     heap_libc_realloc,
    146     heap_libc_free,
    147     NULL,
    148 };
    149 
    150 /* ---------------- diag sink (stderr) ---------------- */
    151 
    152 static const char* diag_label(KitDiagKind k) {
    153   switch (k) {
    154     case KIT_DIAG_NOTE:
    155       return "note";
    156     case KIT_DIAG_WARN:
    157       return "warning";
    158     case KIT_DIAG_ERROR:
    159       return "error";
    160     case KIT_DIAG_FATAL:
    161       return "fatal";
    162   }
    163   return "diag";
    164 }
    165 
    166 /* The compiler currently driving libkit calls is stashed in the stderr sink's
    167  * `user` field so the sink can resolve loc.file_id to the source's spelling
    168  * (path or memory-input label). NULL falls back to the numeric `<file:%u>`
    169  * form. Held on the sink rather than in process-global state so the pointer
    170  * travels with the sink the emit callback is invoked on. */
    171 void driver_diag_set_compiler(KitCompiler* c) { g_diag_stderr.user = c; }
    172 
    173 KitStatus driver_compiler_new(const KitTarget* t, const KitContext* ctx,
    174                               KitCompiler** out) {
    175   KitCompiler* c = NULL;
    176   KitStatus st = kit_compiler_new(t, ctx, &c);
    177   if (st != KIT_OK) {
    178     if (out) *out = NULL;
    179     return st;
    180   }
    181   driver_diag_set_compiler(c);
    182   if (out) *out = c;
    183   return KIT_OK;
    184 }
    185 
    186 void driver_compiler_free(KitCompiler* c) {
    187   if (!c) return;
    188   if (g_diag_stderr.user == c) driver_diag_set_compiler(NULL);
    189   kit_compiler_free(c);
    190 }
    191 
    192 static void diag_stderr_emit(KitDiagSink* s, KitDiagKind k, KitSrcLoc loc,
    193                              const char* fmt, va_list ap) {
    194   KitCompiler* compiler = s ? (KitCompiler*)s->user : NULL;
    195   if (loc.file_id || loc.line) {
    196     KitSlice name = kit_compiler_file_name(compiler, loc.file_id);
    197     if (name.len) {
    198       fprintf(stderr, "%.*s:%u:%u: %.*s: ", KIT_SLICE_ARG(name), loc.line,
    199               loc.col, KIT_SLICE_ARG(kit_slice_cstr(diag_label(k))));
    200     } else {
    201       fprintf(stderr, "<file:%u>:%u:%u: %.*s: ", loc.file_id, loc.line, loc.col,
    202               KIT_SLICE_ARG(kit_slice_cstr(diag_label(k))));
    203     }
    204   } else {
    205     fprintf(stderr, "%.*s: ", KIT_SLICE_ARG(kit_slice_cstr(diag_label(k))));
    206   }
    207   vfprintf(stderr, fmt, ap);
    208   fputc('\n', stderr);
    209 }
    210 
    211 KitDiagSink g_diag_stderr = {
    212     diag_stderr_emit,
    213     NULL,
    214     0,
    215     0,
    216 };
    217 
    218 /* Driver-level post-compile diagnostic gate. The C frontend currently ignores
    219  * KitDiagnosticOptions, so the two warning policies are enforced here, over the
    220  * counts libkit maintains on the diag sink:
    221  *   -Werror      : a successful compile with any emitted warning becomes a
    222  *                  failure (the frontend kept compiling; we fail the tool).
    223  *   -fmax-errors : not yet honored mid-compile by the frontend, so rather than
    224  *                  silently accept it we emit a single explanatory note.
    225  * Returns nonzero when the run should be treated as failed. */
    226 int driver_diag_finish(DriverEnv* env, const char* tool,
    227                        int warnings_are_errors, uint32_t max_errors) {
    228   KitDiagSink* sink = env ? env->diag : NULL;
    229   uint32_t warnings = sink ? sink->warnings : 0u;
    230   if (max_errors) {
    231     driver_errf(tool,
    232                 "note: -fmax-errors is unimplemented; it does not bound the "
    233                 "error count");
    234   }
    235   if (warnings_are_errors && warnings) {
    236     driver_errf(tool, "%u warning%s treated as error%s (-Werror)", warnings,
    237                 warnings == 1u ? "" : "s", warnings == 1u ? "" : "s");
    238     return 1;
    239   }
    240   return 0;
    241 }
    242 
    243 /* ---------------- alloc helpers ---------------- */
    244 
    245 void* driver_alloc(DriverEnv* e, size_t n) {
    246   return e->heap->alloc(e->heap, n, _Alignof(max_align_t));
    247 }
    248 
    249 void* driver_alloc_zeroed(DriverEnv* e, size_t n) {
    250   void* p = driver_alloc(e, n);
    251   if (p) memset(p, 0, n);
    252   return p;
    253 }
    254 
    255 void driver_free(DriverEnv* e, void* p, size_t n) {
    256   if (p) e->heap->free(e->heap, p, n);
    257 }
    258 
    259 void driver_memcpy(void* dst, const void* src, size_t n) {
    260   memcpy(dst, src, n);
    261 }
    262 
    263 /* ---------------- file load/release (OS-neutral) ---------------- */
    264 
    265 /* Pure file_io vtable bookkeeping: no per-host behavior, so it lives here
    266  * rather than being duplicated in each env/<host>.c. */
    267 
    268 int driver_load_bytes(const KitFileIO* io, const char* tool, const char* path,
    269                       DriverLoad* out, KitSlice* in) {
    270   out->loaded = 0;
    271   out->fd.data = NULL;
    272   out->fd.size = 0;
    273   out->fd.token = NULL;
    274   if (!io || !io->read_all) {
    275     driver_errf(tool, "host file I/O unavailable");
    276     return 1;
    277   }
    278   if (io->read_all(io->user, path, &out->fd) != KIT_OK) {
    279     driver_errf(tool, "failed to read: %.*s",
    280                 KIT_SLICE_ARG(kit_slice_cstr(path)));
    281     return 1;
    282   }
    283   out->loaded = 1;
    284   in->data = out->fd.data;
    285   in->len = out->fd.size;
    286   return 0;
    287 }
    288 
    289 void driver_release_bytes(const KitFileIO* io, DriverLoad* lf) {
    290   if (!lf || !lf->loaded) return;
    291   if (io && io->release) io->release(io->user, &lf->fd);
    292   lf->loaded = 0;
    293 }
    294 
    295 /* ---------------- hosted dir lists ---------------- */
    296 
    297 int driver_hosted_dirs_add_inc(DriverHostedDirs* d, const char* dir) {
    298   return kit_os_hosted_dirs_add_inc(d, dir);
    299 }
    300 
    301 int driver_hosted_dirs_add_lib(DriverHostedDirs* d, const char* dir) {
    302   return kit_os_hosted_dirs_add_lib(d, dir);
    303 }
    304 
    305 int driver_hosted_dirs_add_inc_join(DriverHostedDirs* d, const char* base,
    306                                     const char* sub) {
    307   return kit_os_hosted_dirs_add_inc_join(d, base, sub);
    308 }
    309 
    310 int driver_hosted_dirs_add_lib_join(DriverHostedDirs* d, const char* base,
    311                                     const char* sub) {
    312   return kit_os_hosted_dirs_add_lib_join(d, base, sub);
    313 }
    314 
    315 void driver_hosted_dirs_fini(DriverHostedDirs* d) {
    316   kit_os_hosted_dirs_fini(d);
    317 }
    318 
    319 /* ---------------- string predicates ---------------- */
    320 
    321 /* The driver's only NUL-terminated-string handling: thin boundary shims
    322  * that route every length scan through kit_slice_cstr and otherwise use
    323  * the length-based mem* primitives. No libc str* is used. */
    324 
    325 int driver_streq(const char* a, const char* b) {
    326   return kit_slice_eq(kit_slice_cstr(a), kit_slice_cstr(b));
    327 }
    328 
    329 int driver_strneq(const char* a, const char* b, size_t n) {
    330   size_t i;
    331   for (i = 0; i < n; ++i) {
    332     unsigned char ca = (unsigned char)a[i], cb = (unsigned char)b[i];
    333     if (ca != cb) return 0;
    334     if (ca == '\0') return 1;
    335   }
    336   return 1;
    337 }
    338 
    339 size_t driver_strlen(const char* s) { return kit_slice_cstr(s).len; }
    340 
    341 const char* driver_strchr(const char* s, int c) {
    342   /* search includes the terminator so driver_strchr(s, 0) works like strchr */
    343   return (const char*)memchr(s, c, kit_slice_cstr(s).len + 1u);
    344 }
    345 
    346 const char* driver_basename(const char* path) {
    347   size_t i = kit_slice_cstr(path).len;
    348   while (i > 0) {
    349     /* Accept both separators so a Windows argv[0] like "C:\\bin\\kit.exe"
    350      * strips to its basename rather than returning the full path. */
    351     if (path[i - 1] == '/' || path[i - 1] == '\\') return path + i;
    352     --i;
    353   }
    354   return path;
    355 }
    356 
    357 int driver_has_suffix(const char* s, const char* suffix) {
    358   size_t ls = kit_slice_cstr(s).len;
    359   size_t lf = kit_slice_cstr(suffix).len;
    360   return ls >= lf && memcmp(s + ls - lf, suffix, lf) == 0;
    361 }
    362 
    363 /* ---------------- scalar parsing helpers ---------------- */
    364 
    365 int driver_hex_nibble(char c) {
    366   if (c >= '0' && c <= '9') return c - '0';
    367   if (c >= 'a' && c <= 'f') return 10 + (c - 'a');
    368   if (c >= 'A' && c <= 'F') return 10 + (c - 'A');
    369   return -1;
    370 }
    371 
    372 uint64_t driver_epoch_from_env(void) {
    373   const char* s = driver_getenv("SOURCE_DATE_EPOCH");
    374   uint64_t v = 0;
    375   if (!s || !*s) return 0;
    376   for (; *s; ++s) {
    377     if (*s < '0' || *s > '9') return 0;
    378     v = v * 10 + (uint64_t)(*s - '0');
    379   }
    380   return v;
    381 }
    382 
    383 int driver_parse_u64(const char* s, uint64_t* out) {
    384   uint64_t v = 0;
    385   int base = 10;
    386   if (!s || !*s) return 1;
    387   if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
    388     base = 16;
    389     s += 2;
    390     if (!*s) return 1;
    391   }
    392   for (; *s; ++s) {
    393     int d = driver_hex_nibble(*s);
    394     if (d < 0 || d >= base) return 1;
    395     if (v > (UINT64_MAX - (uint64_t)d) / (uint64_t)base) return 1;
    396     v = v * (uint64_t)base + (uint64_t)d;
    397   }
    398   *out = v;
    399   return 0;
    400 }
    401 
    402 static uint16_t driver_edit_distance(const char* a, size_t an, const char* b,
    403                                      size_t bn) {
    404   uint16_t prev[65];
    405   uint16_t curr[65];
    406   size_t i, j;
    407   for (j = 0; j <= bn; ++j) prev[j] = (uint16_t)j;
    408   for (i = 1; i <= an; ++i) {
    409     curr[0] = (uint16_t)i;
    410     for (j = 1; j <= bn; ++j) {
    411       uint16_t del = (uint16_t)(prev[j] + 1u);
    412       uint16_t ins = (uint16_t)(curr[j - 1u] + 1u);
    413       uint16_t sub = (uint16_t)(prev[j - 1u] + (a[i - 1u] != b[j - 1u]));
    414       uint16_t best = del < ins ? del : ins;
    415       if (sub < best) best = sub;
    416       curr[j] = best;
    417     }
    418     for (j = 0; j <= bn; ++j) prev[j] = curr[j];
    419   }
    420   return prev[bn];
    421 }
    422 
    423 size_t driver_suggest_values(const char* input, const char* const* candidates,
    424                              size_t candidate_count, DriverSuggestion* out,
    425                              size_t out_cap) {
    426   size_t input_len, i, count = 0;
    427   if (!input || !candidates || !out || out_cap == 0) return 0;
    428   input_len = driver_strlen(input);
    429   if (input_len == 0 || input_len > 64) return 0;
    430   for (i = 0; i < candidate_count; ++i) {
    431     const char* candidate = candidates[i];
    432     size_t candidate_len, max_len, pos;
    433     uint16_t distance, threshold;
    434     if (!candidate) continue;
    435     candidate_len = driver_strlen(candidate);
    436     if (candidate_len == 0 || candidate_len > 64) continue;
    437     distance = driver_edit_distance(input, input_len, candidate, candidate_len);
    438     if (distance == 0) continue;
    439     max_len = input_len > candidate_len ? input_len : candidate_len;
    440     threshold = (uint16_t)(max_len <= 2 ? 1 : (max_len <= 8 ? 2 : 3));
    441     if (distance > threshold) continue;
    442 
    443     pos = 0;
    444     while (pos < count && out[pos].distance <= distance) ++pos;
    445     if (pos >= out_cap) continue;
    446     if (count < out_cap) ++count;
    447     for (size_t move = count - 1u; move > pos; --move) out[move] = out[move - 1u];
    448     out[pos].value = candidate;
    449     out[pos].distance = distance;
    450   }
    451   return count;
    452 }
    453 
    454 char* driver_path_join(DriverEnv* env, const char* a, const char* b,
    455                        size_t* out_size) {
    456   size_t al = a ? driver_strlen(a) : 0u;
    457   size_t bl = b ? driver_strlen(b) : 0u;
    458   /* Insert a '/' only when there is a `b` to separate from `a` and `a` does
    459    * not already end in a separator -- a NULL/empty `b` yields a plain copy of
    460    * `a` with no trailing slash. */
    461   size_t slash =
    462       (bl && al > 0 && a[al - 1u] != '/' && a[al - 1u] != '\\') ? 1u : 0u;
    463   size_t bytes = al + slash + bl + 1u;
    464   char* out = (char*)driver_alloc(env, bytes);
    465   size_t off = 0;
    466   if (!out) return NULL;
    467   if (al) {
    468     driver_memcpy(out, a, al);
    469     off = al;
    470   }
    471   if (slash) out[off++] = '/';
    472   if (bl) {
    473     driver_memcpy(out + off, b, bl);
    474     off += bl;
    475   }
    476   out[off] = '\0';
    477   if (out_size) *out_size = bytes;
    478   return out;
    479 }
    480 
    481 /* ---------------- printf/errf/logf ---------------- */
    482 
    483 void driver_errf(const char* tool, const char* fmt, ...) {
    484   va_list ap;
    485   va_start(ap, fmt);
    486   driver_verrf(tool, fmt, ap);
    487   va_end(ap);
    488 }
    489 
    490 void driver_verrf(const char* tool, const char* fmt, va_list ap) {
    491   fprintf(stderr, "%.*s: ", KIT_SLICE_ARG(kit_slice_cstr(tool)));
    492   vfprintf(stderr, fmt, ap);
    493   fputc('\n', stderr);
    494 }
    495 
    496 void driver_logf(const char* fmt, ...) {
    497   va_list ap;
    498   va_start(ap, fmt);
    499   vfprintf(stderr, fmt, ap);
    500   va_end(ap);
    501   fputc('\n', stderr);
    502 }
    503 
    504 void kit_debug_printf(const char* fmt, ...) {
    505   va_list ap;
    506   va_start(ap, fmt);
    507   vfprintf(stderr, fmt, ap);
    508   va_end(ap);
    509 }
    510 
    511 const char* kit_debug_getenv(const char* name) { return getenv(name); }
    512 
    513 /* ---------------- tracing (KIT_TRACE) ---------------- */
    514 
    515 /* Hosted side of the kit/trace.h seam: parse KIT_TRACE once, cache it, and
    516  * answer kit_trace_enabled / format kit_trace_emit. The config is immutable
    517  * after the first read, so the lazy init is intentionally lock-free --
    518  * concurrent first-callers re-parse identical data into the same cache, which
    519  * is idempotent. Tracing is diagnostic-only and never affects compiler
    520  * output, so this process-scoped state is the one place trace state lives. */
    521 
    522 #define TRACE_MAX_SPECS 32
    523 #define TRACE_BUF_CAP 512
    524 
    525 typedef struct TraceSpec {
    526   const char* module; /* substring matched against the trace point's module */
    527   int level;          /* threshold 1..5 (KitTraceLevel) */
    528 } TraceSpec;
    529 
    530 static struct {
    531   int parsed;
    532   int global_level; /* threshold for modules with no matching spec; 0 = off */
    533   int nspecs;
    534   TraceSpec specs[TRACE_MAX_SPECS];
    535   char buf[TRACE_BUF_CAP]; /* owns the tokenized copy the specs point into */
    536 } g_trace;
    537 
    538 static int trace_level_from_name(const char* s, size_t n) {
    539   if (n == 5 && memcmp(s, "error", 5) == 0) return KIT_TRACE_ERROR;
    540   if (n == 4 && memcmp(s, "warn", 4) == 0) return KIT_TRACE_WARN;
    541   if (n == 7 && memcmp(s, "warning", 7) == 0) return KIT_TRACE_WARN;
    542   if (n == 4 && memcmp(s, "info", 4) == 0) return KIT_TRACE_INFO;
    543   if (n == 5 && memcmp(s, "debug", 5) == 0) return KIT_TRACE_DEBUG;
    544   if (n == 5 && memcmp(s, "trace", 5) == 0) return KIT_TRACE_TRACE;
    545   return 0;
    546 }
    547 
    548 /* "1" / "*" / "all" are spellings of "everything, max verbosity". */
    549 static int trace_is_all(const char* s, size_t n) {
    550   return (n == 1 && (s[0] == '1' || s[0] == '*')) ||
    551          (n == 3 && memcmp(s, "all", 3) == 0);
    552 }
    553 
    554 static void trace_raise_global(int lvl) {
    555   if (lvl > g_trace.global_level) g_trace.global_level = lvl;
    556 }
    557 
    558 static void trace_add_module(const char* module, int lvl) {
    559   if (g_trace.nspecs >= TRACE_MAX_SPECS) return;
    560   g_trace.specs[g_trace.nspecs].module = module;
    561   g_trace.specs[g_trace.nspecs].level = lvl;
    562   g_trace.nspecs++;
    563 }
    564 
    565 static void trace_parse(void) {
    566   const char* env = getenv("KIT_TRACE");
    567   size_t len, i, start;
    568 
    569   g_trace.parsed = 1;
    570   g_trace.global_level = 0;
    571   g_trace.nspecs = 0;
    572   if (!env || !env[0]) return;
    573 
    574   len = strlen(env);
    575   if (len >= TRACE_BUF_CAP) len = TRACE_BUF_CAP - 1;
    576   memcpy(g_trace.buf, env, len);
    577   g_trace.buf[len] = '\0';
    578 
    579   /* Split on ',' in place; each token is "[module=]level" or a bare token. */
    580   start = 0;
    581   for (i = 0; i <= len; ++i) {
    582     char* tok;
    583     size_t tlen;
    584     char* eq;
    585     if (i != len && g_trace.buf[i] != ',') continue;
    586     g_trace.buf[i] = '\0';
    587     tok = &g_trace.buf[start];
    588     tlen = i - start;
    589     start = i + 1;
    590     if (tlen == 0) continue;
    591 
    592     eq = memchr(tok, '=', tlen);
    593     if (eq) {
    594       size_t mlen = (size_t)(eq - tok);
    595       const char* lvls = eq + 1;
    596       size_t llen = tlen - mlen - 1;
    597       int lvl = trace_level_from_name(lvls, llen);
    598       if (lvl == 0 && trace_is_all(lvls, llen)) lvl = KIT_TRACE_TRACE;
    599       if (lvl == 0) continue; /* unknown level name: ignore the spec */
    600       *eq = '\0';             /* terminate the module substring in place */
    601       if (mlen == 0)
    602         trace_raise_global(lvl);
    603       else
    604         trace_add_module(tok, lvl);
    605     } else {
    606       int lvl = trace_level_from_name(tok, tlen);
    607       if (trace_is_all(tok, tlen)) lvl = KIT_TRACE_TRACE;
    608       if (lvl != 0)
    609         trace_raise_global(lvl); /* bare level => default for all modules */
    610       else
    611         trace_add_module(tok, KIT_TRACE_TRACE); /* bare module => at TRACE */
    612     }
    613   }
    614 }
    615 
    616 int kit_trace_enabled(const char* module, int level) {
    617   int threshold, i;
    618   if (!g_trace.parsed) trace_parse();
    619   threshold = g_trace.global_level;
    620   if (module) {
    621     /* Most-verbose matching module spec wins, falling back to the global. */
    622     for (i = 0; i < g_trace.nspecs; ++i)
    623       if (g_trace.specs[i].level > threshold &&
    624           strstr(module, g_trace.specs[i].module) != NULL)
    625         threshold = g_trace.specs[i].level;
    626   }
    627   return threshold > 0 && level <= threshold;
    628 }
    629 
    630 static char trace_level_char(int level) {
    631   switch (level) {
    632     case KIT_TRACE_ERROR:
    633       return 'E';
    634     case KIT_TRACE_WARN:
    635       return 'W';
    636     case KIT_TRACE_INFO:
    637       return 'I';
    638     case KIT_TRACE_DEBUG:
    639       return 'D';
    640     case KIT_TRACE_TRACE:
    641       return 'T';
    642     default:
    643       return '?';
    644   }
    645 }
    646 
    647 void kit_trace_emit(const char* module, int level, const char* file, int line,
    648                     const char* fmt, ...) {
    649   va_list ap;
    650   char lc = trace_level_char(level);
    651   if (!file) file = "?";
    652   /* Show the module tag unless it is just the source path (the default). */
    653   if (module && strcmp(module, file) != 0)
    654     fprintf(stderr, "[%c][%s] %s:%d: ", lc, module, file, line);
    655   else
    656     fprintf(stderr, "[%c] %s:%d: ", lc, file, line);
    657   va_start(ap, fmt);
    658   vfprintf(stderr, fmt, ap);
    659   va_end(ap);
    660   fputc('\n', stderr);
    661 }
    662 
    663 void driver_printf(const char* fmt, ...) {
    664   va_list ap;
    665   va_start(ap, fmt);
    666   vprintf(fmt, ap);
    667   va_end(ap);
    668 }
    669 
    670 void driver_flush_stdout(void) { fflush(stdout); }
    671 
    672 const char* driver_getenv(const char* name) { return getenv(name); }
    673 
    674 int driver_line_completion_add(DriverLineCompletionList* l, const char* text,
    675                                size_t len) {
    676   DriverLineCompletion* ni;
    677   char* copy;
    678   uint32_t nc;
    679   size_t old_size;
    680   size_t new_size;
    681   if (!l || !l->env || !text) return 1;
    682   if (l->count == l->cap) {
    683     nc = l->cap ? l->cap * 2u : 16u;
    684     old_size = (size_t)l->cap * sizeof(*l->items);
    685     new_size = (size_t)nc * sizeof(*l->items);
    686     ni = (DriverLineCompletion*)l->env->heap->realloc(
    687         l->env->heap, l->items, old_size, new_size,
    688         _Alignof(DriverLineCompletion));
    689     if (!ni) return 1;
    690     l->items = ni;
    691     l->cap = nc;
    692   }
    693   copy = (char*)driver_alloc(l->env, len + 1u);
    694   if (!copy) return 1;
    695   driver_memcpy(copy, text, len);
    696   copy[len] = '\0';
    697   l->items[l->count].text = copy;
    698   l->items[l->count].size = len + 1u;
    699   l->count++;
    700   return 0;
    701 }
    702 
    703 void driver_line_history_fini(DriverEnv* env, DriverLineHistory* h) {
    704   uint32_t i;
    705   if (!env || !h) return;
    706   for (i = 0; i < h->count; ++i) {
    707     if (h->items && h->items[i]) driver_free(env, h->items[i], h->sizes[i]);
    708   }
    709   if (h->items) driver_free(env, h->items, (size_t)h->cap * sizeof(*h->items));
    710   if (h->sizes) driver_free(env, h->sizes, (size_t)h->cap * sizeof(*h->sizes));
    711   {
    712     DriverLineHistory z = {0};
    713     *h = z;
    714   }
    715 }