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 }