kit

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

common.c (14093B)


      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 <stdarg.h>
      6 #include <stdint.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <string.h>
     10 
     11 #include "env_internal.h"
     12 
     13 /* ---------------- heap (libc-backed) ---------------- */
     14 
     15 static void* heap_libc_alloc(KitHeap* h, size_t size, size_t align) {
     16   (void)h;
     17   (void)align; /* malloc satisfies all max_align_t alignments */
     18   return size ? malloc(size) : NULL;
     19 }
     20 
     21 static void* heap_libc_realloc(KitHeap* h, void* p, size_t old_size,
     22                                size_t new_size, size_t align) {
     23   (void)h;
     24   (void)old_size;
     25   (void)align;
     26   return realloc(p, new_size);
     27 }
     28 
     29 static void heap_libc_free(KitHeap* h, void* p, size_t size) {
     30   (void)h;
     31   (void)size;
     32   free(p);
     33 }
     34 
     35 KitHeap g_heap_libc = {
     36     heap_libc_alloc,
     37     heap_libc_realloc,
     38     heap_libc_free,
     39     NULL,
     40 };
     41 
     42 /* ---------------- diag sink (stderr) ---------------- */
     43 
     44 static const char* diag_label(KitDiagKind k) {
     45   switch (k) {
     46     case KIT_DIAG_NOTE:
     47       return "note";
     48     case KIT_DIAG_WARN:
     49       return "warning";
     50     case KIT_DIAG_ERROR:
     51       return "error";
     52     case KIT_DIAG_FATAL:
     53       return "fatal";
     54   }
     55   return "diag";
     56 }
     57 
     58 /* Tracks the compiler currently driving libkit calls so the stderr
     59  * diag sink can resolve loc.file_id to the source's spelling (path or
     60  * memory-input label). NULL falls back to the numeric `<file:%u>` form. */
     61 static KitCompiler* g_diag_active_compiler;
     62 
     63 void driver_diag_set_compiler(KitCompiler* c) { g_diag_active_compiler = c; }
     64 
     65 KitStatus driver_compiler_new(const KitTarget* t, const KitContext* ctx,
     66                               KitCompiler** out) {
     67   KitCompiler* c = NULL;
     68   KitStatus st = kit_compiler_new(t, ctx, &c);
     69   if (st != KIT_OK) {
     70     if (out) *out = NULL;
     71     return st;
     72   }
     73   driver_diag_set_compiler(c);
     74   if (out) *out = c;
     75   return KIT_OK;
     76 }
     77 
     78 void driver_compiler_free(KitCompiler* c) {
     79   if (!c) return;
     80   if (g_diag_active_compiler == c) driver_diag_set_compiler(NULL);
     81   kit_compiler_free(c);
     82 }
     83 
     84 static void diag_stderr_emit(KitDiagSink* s, KitDiagKind k, KitSrcLoc loc,
     85                              const char* fmt, va_list ap) {
     86   (void)s;
     87   if (loc.file_id || loc.line) {
     88     KitSlice name = kit_compiler_file_name(g_diag_active_compiler, loc.file_id);
     89     if (name.len) {
     90       fprintf(stderr, "%.*s:%u:%u: %.*s: ", KIT_SLICE_ARG(name), loc.line,
     91               loc.col, KIT_SLICE_ARG(kit_slice_cstr(diag_label(k))));
     92     } else {
     93       fprintf(stderr, "<file:%u>:%u:%u: %.*s: ", loc.file_id, loc.line, loc.col,
     94               KIT_SLICE_ARG(kit_slice_cstr(diag_label(k))));
     95     }
     96   } else {
     97     fprintf(stderr, "%.*s: ", KIT_SLICE_ARG(kit_slice_cstr(diag_label(k))));
     98   }
     99   vfprintf(stderr, fmt, ap);
    100   fputc('\n', stderr);
    101 }
    102 
    103 KitDiagSink g_diag_stderr = {
    104     diag_stderr_emit,
    105     NULL,
    106     0,
    107     0,
    108 };
    109 
    110 /* ---------------- alloc helpers ---------------- */
    111 
    112 void* driver_alloc(DriverEnv* e, size_t n) {
    113   return e->heap->alloc(e->heap, n, _Alignof(max_align_t));
    114 }
    115 
    116 void* driver_alloc_zeroed(DriverEnv* e, size_t n) {
    117   void* p = driver_alloc(e, n);
    118   if (p) memset(p, 0, n);
    119   return p;
    120 }
    121 
    122 void driver_free(DriverEnv* e, void* p, size_t n) {
    123   if (p) e->heap->free(e->heap, p, n);
    124 }
    125 
    126 void driver_memcpy(void* dst, const void* src, size_t n) {
    127   memcpy(dst, src, n);
    128 }
    129 
    130 /* ---------------- hosted dir lists ---------------- */
    131 
    132 /* Mechanical only: join `a` (+ optional `/sub`), dup into the list. No per-OS
    133  * policy lives here -- producers (driver/lib/hosted.c expansion and the
    134  * per-host driver_default_hosted_dirs probes) supply the paths. */
    135 static int hd_store(DriverEnv* env, char** arr, size_t* sizes, uint32_t* n,
    136                     uint32_t cap, const char* a, const char* b) {
    137   size_t alen, blen, slash, bytes, off;
    138   char* out;
    139   if (!a || !a[0]) return 0; /* empty base -> successful no-op */
    140   if (*n >= cap) return 1;   /* loud overflow -- never a silent drop */
    141   alen = driver_strlen(a);
    142   blen = b ? driver_strlen(b) : 0;
    143   slash = (blen && a[alen - 1] != '/') ? 1u : 0u;
    144   bytes = alen + slash + blen + 1u;
    145   out = driver_alloc(env, bytes);
    146   if (!out) return 1;
    147   driver_memcpy(out, a, alen);
    148   off = alen;
    149   if (slash) out[off++] = '/';
    150   if (blen) {
    151     driver_memcpy(out + off, b, blen);
    152     off += blen;
    153   }
    154   out[off] = '\0';
    155   arr[*n] = out;
    156   sizes[*n] = bytes;
    157   (*n)++;
    158   return 0;
    159 }
    160 
    161 int driver_hosted_dirs_add_inc(DriverHostedDirs* d, const char* dir) {
    162   return hd_store(d->env, d->incdirs, d->incdir_sizes, &d->nincdirs,
    163                   DRIVER_HOSTED_MAX_INCDIRS, dir, NULL);
    164 }
    165 
    166 int driver_hosted_dirs_add_lib(DriverHostedDirs* d, const char* dir) {
    167   return hd_store(d->env, d->libdirs, d->libdir_sizes, &d->nlibdirs,
    168                   DRIVER_HOSTED_MAX_LIBDIRS, dir, NULL);
    169 }
    170 
    171 int driver_hosted_dirs_add_inc_join(DriverHostedDirs* d, const char* base,
    172                                     const char* sub) {
    173   return hd_store(d->env, d->incdirs, d->incdir_sizes, &d->nincdirs,
    174                   DRIVER_HOSTED_MAX_INCDIRS, base, sub);
    175 }
    176 
    177 int driver_hosted_dirs_add_lib_join(DriverHostedDirs* d, const char* base,
    178                                     const char* sub) {
    179   return hd_store(d->env, d->libdirs, d->libdir_sizes, &d->nlibdirs,
    180                   DRIVER_HOSTED_MAX_LIBDIRS, base, sub);
    181 }
    182 
    183 void driver_hosted_dirs_fini(DriverHostedDirs* d) {
    184   uint32_t i;
    185   if (!d || !d->env) return;
    186   for (i = 0; i < d->nincdirs; ++i)
    187     driver_free(d->env, d->incdirs[i], d->incdir_sizes[i]);
    188   for (i = 0; i < d->nlibdirs; ++i)
    189     driver_free(d->env, d->libdirs[i], d->libdir_sizes[i]);
    190   d->nincdirs = 0;
    191   d->nlibdirs = 0;
    192 }
    193 
    194 /* ---------------- string predicates ---------------- */
    195 
    196 /* The driver's only NUL-terminated-string handling: thin boundary shims
    197  * that route every length scan through kit_slice_cstr and otherwise use
    198  * the length-based mem* primitives. No libc str* is used. */
    199 
    200 int driver_streq(const char* a, const char* b) {
    201   return kit_slice_eq(kit_slice_cstr(a), kit_slice_cstr(b));
    202 }
    203 
    204 int driver_strneq(const char* a, const char* b, size_t n) {
    205   size_t i;
    206   for (i = 0; i < n; ++i) {
    207     unsigned char ca = (unsigned char)a[i], cb = (unsigned char)b[i];
    208     if (ca != cb) return 0;
    209     if (ca == '\0') return 1;
    210   }
    211   return 1;
    212 }
    213 
    214 size_t driver_strlen(const char* s) { return kit_slice_cstr(s).len; }
    215 
    216 const char* driver_strchr(const char* s, int c) {
    217   /* search includes the terminator so driver_strchr(s, 0) works like strchr */
    218   return (const char*)memchr(s, c, kit_slice_cstr(s).len + 1u);
    219 }
    220 
    221 const char* driver_basename(const char* path) {
    222   size_t i = kit_slice_cstr(path).len;
    223   while (i > 0) {
    224     if (path[i - 1] == '/') return path + i;
    225     --i;
    226   }
    227   return path;
    228 }
    229 
    230 int driver_has_suffix(const char* s, const char* suffix) {
    231   size_t ls = kit_slice_cstr(s).len;
    232   size_t lf = kit_slice_cstr(suffix).len;
    233   return ls >= lf && memcmp(s + ls - lf, suffix, lf) == 0;
    234 }
    235 
    236 /* ---------------- printf/errf/logf ---------------- */
    237 
    238 void driver_errf(const char* tool, const char* fmt, ...) {
    239   va_list ap;
    240   fprintf(stderr, "%.*s: ", KIT_SLICE_ARG(kit_slice_cstr(tool)));
    241   va_start(ap, fmt);
    242   vfprintf(stderr, fmt, ap);
    243   va_end(ap);
    244   fputc('\n', stderr);
    245 }
    246 
    247 void driver_logf(const char* fmt, ...) {
    248   va_list ap;
    249   va_start(ap, fmt);
    250   vfprintf(stderr, fmt, ap);
    251   va_end(ap);
    252   fputc('\n', stderr);
    253 }
    254 
    255 void kit_debug_printf(const char* fmt, ...) {
    256   va_list ap;
    257   va_start(ap, fmt);
    258   vfprintf(stderr, fmt, ap);
    259   va_end(ap);
    260 }
    261 
    262 const char* kit_debug_getenv(const char* name) { return getenv(name); }
    263 
    264 /* ---------------- tracing (KIT_TRACE) ---------------- */
    265 
    266 /* Hosted side of the kit/trace.h seam: parse KIT_TRACE once, cache it, and
    267  * answer kit_trace_enabled / format kit_trace_emit. The config is immutable
    268  * after the first read, so the lazy init is intentionally lock-free --
    269  * concurrent first-callers re-parse identical data into the same cache, which
    270  * is idempotent. Tracing is diagnostic-only and never affects compiler
    271  * output, so this process-scoped state is the one place trace state lives. */
    272 
    273 #define TRACE_MAX_SPECS 32
    274 #define TRACE_BUF_CAP 512
    275 
    276 typedef struct TraceSpec {
    277   const char* module; /* substring matched against the trace point's module */
    278   int level;          /* threshold 1..5 (KitTraceLevel) */
    279 } TraceSpec;
    280 
    281 static struct {
    282   int parsed;
    283   int global_level; /* threshold for modules with no matching spec; 0 = off */
    284   int nspecs;
    285   TraceSpec specs[TRACE_MAX_SPECS];
    286   char buf[TRACE_BUF_CAP]; /* owns the tokenized copy the specs point into */
    287 } g_trace;
    288 
    289 static int trace_level_from_name(const char* s, size_t n) {
    290   if (n == 5 && memcmp(s, "error", 5) == 0) return KIT_TRACE_ERROR;
    291   if (n == 4 && memcmp(s, "warn", 4) == 0) return KIT_TRACE_WARN;
    292   if (n == 7 && memcmp(s, "warning", 7) == 0) return KIT_TRACE_WARN;
    293   if (n == 4 && memcmp(s, "info", 4) == 0) return KIT_TRACE_INFO;
    294   if (n == 5 && memcmp(s, "debug", 5) == 0) return KIT_TRACE_DEBUG;
    295   if (n == 5 && memcmp(s, "trace", 5) == 0) return KIT_TRACE_TRACE;
    296   return 0;
    297 }
    298 
    299 /* "1" / "*" / "all" are spellings of "everything, max verbosity". */
    300 static int trace_is_all(const char* s, size_t n) {
    301   return (n == 1 && (s[0] == '1' || s[0] == '*')) ||
    302          (n == 3 && memcmp(s, "all", 3) == 0);
    303 }
    304 
    305 static void trace_raise_global(int lvl) {
    306   if (lvl > g_trace.global_level) g_trace.global_level = lvl;
    307 }
    308 
    309 static void trace_add_module(const char* module, int lvl) {
    310   if (g_trace.nspecs >= TRACE_MAX_SPECS) return;
    311   g_trace.specs[g_trace.nspecs].module = module;
    312   g_trace.specs[g_trace.nspecs].level = lvl;
    313   g_trace.nspecs++;
    314 }
    315 
    316 static void trace_parse(void) {
    317   const char* env = getenv("KIT_TRACE");
    318   size_t len, i, start;
    319 
    320   g_trace.parsed = 1;
    321   g_trace.global_level = 0;
    322   g_trace.nspecs = 0;
    323   if (!env || !env[0]) return;
    324 
    325   len = strlen(env);
    326   if (len >= TRACE_BUF_CAP) len = TRACE_BUF_CAP - 1;
    327   memcpy(g_trace.buf, env, len);
    328   g_trace.buf[len] = '\0';
    329 
    330   /* Split on ',' in place; each token is "[module=]level" or a bare token. */
    331   start = 0;
    332   for (i = 0; i <= len; ++i) {
    333     char* tok;
    334     size_t tlen;
    335     char* eq;
    336     if (i != len && g_trace.buf[i] != ',') continue;
    337     g_trace.buf[i] = '\0';
    338     tok = &g_trace.buf[start];
    339     tlen = i - start;
    340     start = i + 1;
    341     if (tlen == 0) continue;
    342 
    343     eq = memchr(tok, '=', tlen);
    344     if (eq) {
    345       size_t mlen = (size_t)(eq - tok);
    346       const char* lvls = eq + 1;
    347       size_t llen = tlen - mlen - 1;
    348       int lvl = trace_level_from_name(lvls, llen);
    349       if (lvl == 0 && trace_is_all(lvls, llen)) lvl = KIT_TRACE_TRACE;
    350       if (lvl == 0) continue; /* unknown level name: ignore the spec */
    351       *eq = '\0';             /* terminate the module substring in place */
    352       if (mlen == 0)
    353         trace_raise_global(lvl);
    354       else
    355         trace_add_module(tok, lvl);
    356     } else {
    357       int lvl = trace_level_from_name(tok, tlen);
    358       if (trace_is_all(tok, tlen)) lvl = KIT_TRACE_TRACE;
    359       if (lvl != 0)
    360         trace_raise_global(lvl); /* bare level => default for all modules */
    361       else
    362         trace_add_module(tok, KIT_TRACE_TRACE); /* bare module => at TRACE */
    363     }
    364   }
    365 }
    366 
    367 int kit_trace_enabled(const char* module, int level) {
    368   int threshold, i;
    369   if (!g_trace.parsed) trace_parse();
    370   threshold = g_trace.global_level;
    371   if (module) {
    372     /* Most-verbose matching module spec wins, falling back to the global. */
    373     for (i = 0; i < g_trace.nspecs; ++i)
    374       if (g_trace.specs[i].level > threshold &&
    375           strstr(module, g_trace.specs[i].module) != NULL)
    376         threshold = g_trace.specs[i].level;
    377   }
    378   return threshold > 0 && level <= threshold;
    379 }
    380 
    381 static char trace_level_char(int level) {
    382   switch (level) {
    383     case KIT_TRACE_ERROR:
    384       return 'E';
    385     case KIT_TRACE_WARN:
    386       return 'W';
    387     case KIT_TRACE_INFO:
    388       return 'I';
    389     case KIT_TRACE_DEBUG:
    390       return 'D';
    391     case KIT_TRACE_TRACE:
    392       return 'T';
    393     default:
    394       return '?';
    395   }
    396 }
    397 
    398 void kit_trace_emit(const char* module, int level, const char* file, int line,
    399                     const char* fmt, ...) {
    400   va_list ap;
    401   char lc = trace_level_char(level);
    402   if (!file) file = "?";
    403   /* Show the module tag unless it is just the source path (the default). */
    404   if (module && strcmp(module, file) != 0)
    405     fprintf(stderr, "[%c][%s] %s:%d: ", lc, module, file, line);
    406   else
    407     fprintf(stderr, "[%c] %s:%d: ", lc, file, line);
    408   va_start(ap, fmt);
    409   vfprintf(stderr, fmt, ap);
    410   va_end(ap);
    411   fputc('\n', stderr);
    412 }
    413 
    414 void driver_printf(const char* fmt, ...) {
    415   va_list ap;
    416   va_start(ap, fmt);
    417   vprintf(fmt, ap);
    418   va_end(ap);
    419 }
    420 
    421 void driver_flush_stdout(void) { fflush(stdout); }
    422 
    423 const char* driver_getenv(const char* name) { return getenv(name); }
    424 
    425 int driver_line_completion_add(DriverLineCompletionList* l, const char* text,
    426                                size_t len) {
    427   DriverLineCompletion* ni;
    428   char* copy;
    429   uint32_t nc;
    430   size_t old_size;
    431   size_t new_size;
    432   if (!l || !l->env || !text) return 1;
    433   if (l->count == l->cap) {
    434     nc = l->cap ? l->cap * 2u : 16u;
    435     old_size = (size_t)l->cap * sizeof(*l->items);
    436     new_size = (size_t)nc * sizeof(*l->items);
    437     ni = (DriverLineCompletion*)l->env->heap->realloc(
    438         l->env->heap, l->items, old_size, new_size,
    439         _Alignof(DriverLineCompletion));
    440     if (!ni) return 1;
    441     l->items = ni;
    442     l->cap = nc;
    443   }
    444   copy = (char*)driver_alloc(l->env, len + 1u);
    445   if (!copy) return 1;
    446   driver_memcpy(copy, text, len);
    447   copy[len] = '\0';
    448   l->items[l->count].text = copy;
    449   l->items[l->count].size = len + 1u;
    450   l->count++;
    451   return 0;
    452 }
    453 
    454 void driver_line_history_fini(DriverEnv* env, DriverLineHistory* h) {
    455   uint32_t i;
    456   if (!env || !h) return;
    457   for (i = 0; i < h->count; ++i) {
    458     if (h->items && h->items[i]) driver_free(env, h->items[i], h->sizes[i]);
    459   }
    460   if (h->items) driver_free(env, h->items, (size_t)h->cap * sizeof(*h->items));
    461   if (h->sizes) driver_free(env, h->sizes, (size_t)h->cap * sizeof(*h->sizes));
    462   {
    463     DriverLineHistory z = {0};
    464     *h = z;
    465   }
    466 }