kit

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

commit 769d6ae1ab02a5531923e468ed1a64d69cc3456d
parent 757fa31e7ccda6059b00c27ab30ee4e9b56881b3
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri,  5 Jun 2026 11:51:56 -0700

modularity wave 4: compiler-free language resolution + obj-owned fmt names + jit TLS hook

Closes the last residuals from the audit:
- #6/#7: kit_language_for_name/_for_path/_name + new kit_language_default_extension now accept a
  NULL compiler and resolve against the compile-time default frontend set via a new
  lang_registry_vtable() (kept in lang_registry.c, the one place that checks KIT_LANG_*_ENABLED).
  This unblocks arg-parse-time use. A single driver_path_is_source() authority (registry lookup
  minus C headers) now backs cc/build/run-dbg source classification, replacing four hand-rolled
  extension unions; fixes the latent .S-misclassification bug and keeps the documented
  '.h is a header, not a source' rule in one place. -x names + dbg REPL filenames route through
  the resolvers (driver/cmd/{cc,build,dbg,emu}.c).
- #35: objcopy/objdump get canonical format names from the obj layer (kit_obj_fmt_from_name/_name),
  keeping only the binutils BFD-alias prefix matching + bitwidth suffix as documented tool presentation.
- Beyond the 36 findings, in the same spirit: src/link/link_jit.c Mach-O TLV JIT bootstrap now gates on
  obj_format_tls_via_descriptor instead of target.obj==KIT_OBJ_MACHO.

Build + tests green: link 122, driver-cc 98, build 56, tools 45, objcopy 5, ar 6, toy 1378,
smoke x64+rv64. (Pre-existing, unrelated: objdump aarch64/04-disasm-stripped-segment stale golden —
fails identically on baseline 30367860; untouched disassembler, fixed fixture.)

Diffstat:
Mdriver/cmd/build.c | 27+++++++++++----------------
Mdriver/cmd/cc.c | 35+++++++++++++++--------------------
Mdriver/cmd/dbg.c | 194++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mdriver/cmd/objcopy.c | 6++++++
Mdriver/cmd/objdump.c | 47++++++++++++++++++++++++++++-------------------
Mdriver/driver.h | 8++++++++
Mdriver/lib/inputs.c | 22+++++++++++++++++++---
Minclude/kit/compile.h | 26+++++++++++++++++++++++---
Msrc/api/compile.c | 36++++++++++++++++++++++++++++++------
Msrc/api/lang_registry.c | 24++++++++++++++++++++++++
Msrc/api/lang_registry.h | 8++++++++
Msrc/link/link_jit.c | 7+++++--
12 files changed, 286 insertions(+), 154 deletions(-)

diff --git a/driver/cmd/build.c b/driver/cmd/build.c @@ -241,17 +241,12 @@ static int build_record_mcmodel(BuildOptions* o, const char* val) { } static int build_lang_from_name(const char* name, KitLanguage* out) { - if (driver_streq(name, "c")) { - *out = KIT_LANG_C; - } else if (driver_streq(name, "asm") || driver_streq(name, "s")) { - *out = KIT_LANG_ASM; - } else if (driver_streq(name, "toy")) { - *out = KIT_LANG_TOY; - } else if (driver_streq(name, "wasm") || driver_streq(name, "wat")) { - *out = KIT_LANG_WASM; - } else { - return 1; - } + /* Resolve off the compile-time default frontend set (no compiler exists at + * arg-parse time). Value-equivalent to the former explicit map: + * c->C, asm/s->ASM, toy->TOY, wasm/wat->WASM. */ + KitLanguage lang = kit_language_for_name(NULL, name); + if (lang == KIT_LANG_UNKNOWN) return 1; + *out = lang; return 0; } @@ -422,11 +417,11 @@ static void build_insert_runtime_archive(BuildOptions* o, /* positional classification */ /* ===================================================================== */ -static int build_is_source(const char* s) { - return driver_has_suffix(s, ".c") || driver_has_suffix(s, ".S") || - driver_has_suffix(s, ".s") || driver_has_suffix(s, ".toy") || - driver_has_suffix(s, ".wat") || driver_has_suffix(s, ".wasm"); -} +/* Routed through the shared driver_path_is_source authority (canonical + * extension registry, headers excluded), so adding a frontend extension reaches + * cc/build/run/dbg at once. build still treats .h as a header, not a source — + * the helper excludes it. */ +static int build_is_source(const char* s) { return driver_path_is_source(s); } static int build_is_dso(const char* s) { return driver_has_suffix(s, ".so") || driver_has_suffix(s, ".dylib") || diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c @@ -363,11 +363,11 @@ static void cc_options_release(CcOptions* o) { static int cc_apply_hosted_profile(CcOptions* o); -static int cc_is_c_source(const char* s) { - return driver_has_suffix(s, ".c") || driver_has_suffix(s, ".S") || - driver_has_suffix(s, ".s") || driver_has_suffix(s, ".toy") || - driver_has_suffix(s, ".wat") || driver_has_suffix(s, ".wasm"); -} +/* Routed through the shared driver_path_is_source authority (canonical + * extension registry, headers excluded), so adding a frontend extension reaches + * cc/build/run/dbg at once. cc still treats .h as a header, not a source — the + * helper excludes it. */ +static int cc_is_c_source(const char* s) { return driver_path_is_source(s); } static int cc_is_dso_input(const char* s) { return driver_has_suffix(s, ".so") || driver_has_suffix(s, ".dylib") || @@ -1359,26 +1359,21 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { forced_lang = -1; continue; } - if (driver_streq(argv[i], "c")) { - forced_lang = KIT_LANG_C; - continue; - } - if (driver_streq(argv[i], "asm") || driver_streq(argv[i], "s")) { - forced_lang = KIT_LANG_ASM; - continue; - } + /* cc-only CLI aliases that have no frontend `-x` name: GCC's "asm-cpp" + * and the case-sensitive "S" (distinct from "s") both select asm. */ if (driver_streq(argv[i], "asm-cpp") || driver_streq(argv[i], "S")) { forced_lang = KIT_LANG_ASM; continue; } - if (driver_streq(argv[i], "wasm") || driver_streq(argv[i], "wat")) { - forced_lang = KIT_LANG_WASM; - continue; - } { - driver_errf(CC_TOOL, "unsupported -x language: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(argv[i]))); - return 1; + KitLanguage lang = kit_language_for_name(NULL, argv[i]); + if (lang == KIT_LANG_UNKNOWN) { + driver_errf(CC_TOOL, "unsupported -x language: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(argv[i]))); + return 1; + } + forced_lang = lang; + continue; } } if (driver_strneq(a, "-L", 2)) { diff --git a/driver/cmd/dbg.c b/driver/cmd/dbg.c @@ -172,24 +172,15 @@ static int dbg_alloc_arrays(DbgOpts* o, int argc) { } static int dbg_parse_language_name(const char* name, KitLanguage* out) { + /* Resolve off the compile-time default frontend set (no compiler exists at + * arg-parse time). Value-equivalent to the former explicit map: + * c->C, toy->TOY, asm/s->ASM, wasm/wat->WASM. */ + KitLanguage lang; if (!name || !*name || !out) return 0; - if (driver_streq(name, "c")) { - *out = KIT_LANG_C; - return 1; - } - if (driver_streq(name, "toy")) { - *out = KIT_LANG_TOY; - return 1; - } - if (driver_streq(name, "asm") || driver_streq(name, "s")) { - *out = KIT_LANG_ASM; - return 1; - } - if (driver_streq(name, "wasm") || driver_streq(name, "wat")) { - *out = KIT_LANG_WASM; - return 1; - } - return 0; + lang = kit_language_for_name(NULL, name); + if (lang == KIT_LANG_UNKNOWN) return 0; + *out = lang; + return 1; } static int dbg_set_default_language(DbgOpts* o, const char* name) { @@ -391,6 +382,9 @@ typedef struct DbgState { const char* entry_name; KitLanguage default_jit_lang; const char* default_jit_name; + /* Backing storage for default_jit_name (built from the canonical frontend + * extension), kept stable for the session's lifetime. */ + char default_jit_name_buf[32]; KitCompileSession* compile_sessions[KIT_LANG_COUNT]; DbgSource* sources; uint32_t nsources; @@ -1772,62 +1766,81 @@ static int dbg_brace_delta(const char* p) { return d; } -static const char* dbg_jit_language_name(KitLanguage lang) { - switch (lang) { - case KIT_LANG_ASM: - return "asm"; - case KIT_LANG_TOY: - return "toy"; - case KIT_LANG_WASM: - return "wasm"; - case KIT_LANG_C: - case KIT_LANG_UNKNOWN: - case KIT_LANG_COUNT: - break; - } - return "c"; -} - -static const char* dbg_jit_language_suffix(KitLanguage lang) { - switch (lang) { - case KIT_LANG_ASM: - return ".s"; - case KIT_LANG_TOY: - return ".toy"; - case KIT_LANG_WASM: - return ".wat"; - case KIT_LANG_C: - case KIT_LANG_UNKNOWN: - case KIT_LANG_COUNT: - break; - } - return ".c"; -} - -static const char* dbg_jit_default_name(KitLanguage lang) { - switch (lang) { - case KIT_LANG_ASM: - return "<dbg-jit.s>"; - case KIT_LANG_TOY: - return "<dbg-jit.toy>"; - case KIT_LANG_WASM: - return "<dbg-jit.wat>"; - case KIT_LANG_C: - case KIT_LANG_UNKNOWN: - case KIT_LANG_COUNT: - break; - } - return "<dbg-jit.c>"; -} - -static int dbg_jit_uses_default_name(KitLanguage lang, const char* input_name) { - return !input_name || driver_streq(input_name, dbg_jit_default_name(lang)); -} - -static int dbg_make_repl_source_name(KitLanguage lang, uint64_t id, char* out, - size_t cap) { +/* Caller buffer sizes for the REPL filename builders below. ".<ext>" and + * "<dbg-jit.<ext>>" with the canonical frontend extension comfortably fit. */ +#define DBG_JIT_SUFFIX_CAP 16 +#define DBG_JIT_NAME_CAP 32 +/* DbgState.default_jit_name_buf must match DBG_JIT_NAME_CAP (it is sized with a + * literal because it precedes this macro in the file). */ +_Static_assert(sizeof(((DbgState*)0)->default_jit_name_buf) == DBG_JIT_NAME_CAP, + "default_jit_name_buf must equal DBG_JIT_NAME_CAP"); + +/* Canonical language name for the JIT REPL (kit_language_name with a "c" + * fallback so C and any unnamed/out-of-range language render as "c", matching + * the former hardcoded default). The result is borrowed static storage. */ +static const char* dbg_jit_language_name(KitCompiler* c, KitLanguage lang) { + const char* name = kit_language_name(c, lang); + return name ? name : "c"; +} + +/* Append NUL-terminated `s` into `buf[cap]` starting at `*pos`, advancing + * `*pos`. Truncates rather than overflowing; the result is always + * NUL-terminated when cap > 0. (dbg.c builds strings with driver_* helpers + * rather than stdio.) */ +static void dbg_str_append(char* buf, size_t cap, size_t* pos, const char* s) { + size_t n = driver_strlen(s); + size_t room; + if (*pos >= cap) return; + room = cap - 1u - *pos; /* leave space for NUL */ + if (n > room) n = room; + driver_memcpy(buf + *pos, s, n); + *pos += n; + buf[*pos] = '\0'; +} + +/* Build the dotted file suffix (e.g. ".toy") for `lang` into `buf`, defaulting + * to ".c" for C and any unnamed/out-of-range language. The canonical bare + * extension is the single source of truth (kit_language_default_extension); + * the leading "." is tool presentation. Returns `buf`. */ +static const char* dbg_jit_language_suffix(KitCompiler* c, KitLanguage lang, + char* buf, size_t cap) { + const char* ext = kit_language_default_extension(c, lang); + size_t pos = 0; + if (lang == KIT_LANG_C || !ext) ext = "c"; + if (cap > 0) buf[0] = '\0'; + dbg_str_append(buf, cap, &pos, "."); + dbg_str_append(buf, cap, &pos, ext); + return buf; +} + +/* Build the default synthesized REPL source name (e.g. "<dbg-jit.toy>") for + * `lang` into `buf`, derived from the canonical suffix. Returns `buf`. */ +static const char* dbg_jit_default_name(KitCompiler* c, KitLanguage lang, + char* buf, size_t cap) { + char suffix[DBG_JIT_SUFFIX_CAP]; + size_t pos = 0; + dbg_jit_language_suffix(c, lang, suffix, sizeof suffix); + if (cap > 0) buf[0] = '\0'; + dbg_str_append(buf, cap, &pos, "<dbg-jit"); + dbg_str_append(buf, cap, &pos, suffix); + dbg_str_append(buf, cap, &pos, ">"); + return buf; +} + +static int dbg_jit_uses_default_name(KitCompiler* c, KitLanguage lang, + const char* input_name) { + char def[DBG_JIT_NAME_CAP]; + return !input_name || + driver_streq(input_name, + dbg_jit_default_name(c, lang, def, sizeof def)); +} + +static int dbg_make_repl_source_name(KitCompiler* c, KitLanguage lang, + uint64_t id, char* out, size_t cap) { const char* prefix = "<dbg-jit-"; - const char* suffix = dbg_jit_language_suffix(lang); + char suffix_buf[DBG_JIT_SUFFIX_CAP]; + const char* suffix = + dbg_jit_language_suffix(c, lang, suffix_buf, sizeof suffix_buf); char num[32]; size_t prefix_len = driver_strlen(prefix); size_t suffix_len = driver_strlen(suffix); @@ -1905,15 +1918,18 @@ static int dbg_jit_compile_append_ex(DbgState* s, KitLanguage lang, KitObjBuilder* ob = NULL; KitStatus st; char generated_name[96]; + char default_name[DBG_JIT_NAME_CAP]; const char* effective_name = - input_name ? input_name : dbg_jit_default_name(lang); + input_name ? input_name + : dbg_jit_default_name(s->compiler, lang, default_name, + sizeof default_name); uint64_t source_id = s->source_counter + 1u; int generated_source_name = 0; s->jit_counter++; if (input_kind == KIT_FRONTEND_INPUT_REPL_TOPLEVEL && - dbg_jit_uses_default_name(lang, input_name)) { - if (dbg_make_repl_source_name(lang, source_id, generated_name, + dbg_jit_uses_default_name(s->compiler, lang, input_name)) { + if (dbg_make_repl_source_name(s->compiler, lang, source_id, generated_name, sizeof(generated_name)) != 0) { driver_errf(DBG_TOOL, "repl source name overflow"); return 1; @@ -2094,14 +2110,17 @@ static void dbg_cmd_edit(DbgState* s, const char* rest) { const char* p; uint8_t* src = NULL; size_t len = 0; + char suffix[DBG_JIT_SUFFIX_CAP]; if (dbg_parse_jit_lang_arg(s, rest, &lang, &input_name, &p) != 0) return; if (*p) { driver_errf(DBG_TOOL, "usage: edit [c|toy|asm|name.ext]"); return; } - if (!driver_edit_temp(s->env, dbg_jit_language_suffix(lang), NULL, 0, &src, - &len)) { + if (!driver_edit_temp(s->env, + dbg_jit_language_suffix(s->compiler, lang, suffix, + sizeof suffix), + NULL, 0, &src, &len)) { driver_errf(DBG_TOOL, "editor failed"); return; } @@ -2127,8 +2146,8 @@ static void dbg_cmd_language(DbgState* s, const char* rest) { if (n == 0) { const KitPreprocessOptions* pp = &s->pp; driver_printf("Language: %.*s\n", - KIT_SLICE_ARG(kit_slice_cstr( - dbg_jit_language_name(s->default_jit_lang)))); + KIT_SLICE_ARG(kit_slice_cstr(dbg_jit_language_name( + s->compiler, s->default_jit_lang)))); driver_printf( "Language options: input=%.*s opt=-O%d debug=%.*s includes=%u " "system-includes=%u defines=%u undefines=%u session=%.*s\n", @@ -2156,9 +2175,11 @@ static void dbg_cmd_language(DbgState* s, const char* rest) { return; } s->default_jit_lang = lang; - s->default_jit_name = dbg_jit_default_name(lang); - driver_printf("Language: %.*s\n", - KIT_SLICE_ARG(kit_slice_cstr(dbg_jit_language_name(lang)))); + s->default_jit_name = dbg_jit_default_name( + s->compiler, lang, s->default_jit_name_buf, sizeof s->default_jit_name_buf); + driver_printf( + "Language: %.*s\n", + KIT_SLICE_ARG(kit_slice_cstr(dbg_jit_language_name(s->compiler, lang)))); } static size_t dbg_u64_dec(char* dst, size_t cap, uint64_t v) { @@ -2294,8 +2315,9 @@ static void dbg_cmd_expr(DbgState* s, const char* expr) { size_t len = is_block ? body_len : driver_strlen(expr); KitFrontendInputKind kind = is_block ? KIT_FRONTEND_INPUT_REPL_BLOCK : KIT_FRONTEND_INPUT_REPL_EXPR; - if (dbg_jit_compile_append_ex(s, s->default_jit_lang, - dbg_jit_default_name(s->default_jit_lang), + /* s->default_jit_name mirrors dbg_jit_default_name(s->default_jit_lang), + * kept in sync wherever default_jit_lang is set. */ + if (dbg_jit_compile_append_ex(s, s->default_jit_lang, s->default_jit_name, src, len, kind, name) != 0) { goto out; } @@ -3440,7 +3462,9 @@ int driver_dbg(int argc, char** argv) { dbg_fill_compile_options(&o, &st.copts, &st.pp); st.jit = jit; st.default_jit_lang = dbg_default_language_from_inputs(compiler, &o); - st.default_jit_name = dbg_jit_default_name(st.default_jit_lang); + st.default_jit_name = dbg_jit_default_name( + st.compiler, st.default_jit_lang, st.default_jit_name_buf, + sizeof st.default_jit_name_buf); st.prog_argc = (int)o.prog_argc; st.prog_argv = o.prog_argv; st.entry_name = o.entry; diff --git a/driver/cmd/objcopy.c b/driver/cmd/objcopy.c @@ -204,6 +204,12 @@ static int split_pair(DriverEnv* env, const char* spec, const char** out_left, static int parse_fmt_name(const char* name, KitObjFmt* out) { if (!name) return -1; + /* Canonical bare names (elf/coff/pe/macho/wasm) come from the obj layer's + * single source of truth. */ + if (kit_obj_fmt_from_name(name, out) == KIT_OK) return 0; + /* binutils-name compatibility (not a second source of truth): accept the + * BFD-style decorated spellings objcopy users pass, e.g. "elf64-x86-64", + * "mach-o-arm64", "coff-x86-64", "pe-i386". */ if (driver_strneq(name, "elf", sizeof("elf") - 1)) { *out = KIT_OBJ_ELF; return 0; diff --git a/driver/cmd/objdump.c b/driver/cmd/objdump.c @@ -603,18 +603,19 @@ static void dump_pe_private(const char* label, const uint8_t* buf, } } -static const char* fmt_str(KitObjFmt fmt, uint8_t ptr_size) { - switch (fmt) { - case KIT_OBJ_ELF: - return ptr_size == 8 ? "elf64" : "elf32"; - case KIT_OBJ_COFF: - return ptr_size == 8 ? "coff64" : "coff32"; - case KIT_OBJ_MACHO: - return ptr_size == 8 ? "macho64" : "macho32"; - case KIT_OBJ_WASM: - return "wasm"; - } - return "unknown"; +/* Render objdump's "file format" spelling: the obj layer's canonical bare + * name (elf/coff/macho/wasm) plus the bitwidth suffix this tool presents. + * wasm carries no suffix; an out-of-range fmt renders "unknown". The bare + * name is the single source of truth (kit_obj_fmt_name); the suffix is + * legitimate tool presentation. `buf` must hold at least FMT_STR_CAP bytes. */ +#define FMT_STR_CAP 16 +static const char* fmt_str(KitObjFmt fmt, uint8_t ptr_size, char* buf, + size_t cap) { + const char* base = kit_obj_fmt_name(fmt); + if (!base) return "unknown"; + if (fmt == KIT_OBJ_WASM) return base; /* wasm has no bitwidth suffix */ + snprintf(buf, cap, "%s%s", base, ptr_size == 8 ? "64" : "32"); + return buf; } static const char* arch_str(KitArchKind arch) { @@ -1119,9 +1120,14 @@ static void dump_file_header(KitObjFile* f, const char* label) { if (flags) driver_printf("\n"); driver_printf("start address 0x%016llx\n", have_info ? (unsigned long long)info.entry : 0ull); - driver_printf("format: %.*s, sections: %u, symbols: %u\n\n", - KIT_SLICE_ARG(kit_slice_cstr(fmt_str(fmt, target.ptr_size))), - nsec, nsym); + { + char fmt_buf[FMT_STR_CAP]; + driver_printf( + "format: %.*s, sections: %u, symbols: %u\n\n", + KIT_SLICE_ARG(kit_slice_cstr( + fmt_str(fmt, target.ptr_size, fmt_buf, sizeof fmt_buf))), + nsec, nsym); + } (void)label; } @@ -1595,11 +1601,14 @@ static void dump_obj(const KitContext* ctx, const KitDisasmContext* dctx, const KitSlice* image) { KitTargetSpec target = kit_obj_target(f); KitObjFmt fmt = kit_obj_fmt(f); + char fmt_buf[FMT_STR_CAP]; - driver_printf("%.*s:\tfile format %.*s-%.*s\n\n", - KIT_SLICE_ARG(kit_slice_cstr(label)), - KIT_SLICE_ARG(kit_slice_cstr(fmt_str(fmt, target.ptr_size))), - KIT_SLICE_ARG(kit_slice_cstr(arch_str(target.arch)))); + driver_printf( + "%.*s:\tfile format %.*s-%.*s\n\n", + KIT_SLICE_ARG(kit_slice_cstr(label)), + KIT_SLICE_ARG( + kit_slice_cstr(fmt_str(fmt, target.ptr_size, fmt_buf, sizeof fmt_buf))), + KIT_SLICE_ARG(kit_slice_cstr(arch_str(target.arch)))); if (opts->f) dump_file_header(f, label); if (opts->h) dump_sections(f, opts); diff --git a/driver/driver.h b/driver/driver.h @@ -167,6 +167,14 @@ int driver_target_to_triple(KitTargetSpec target, char* buf, size_t cap); int driver_arch_from_name(const char* name, KitArchKind* arch_out, uint8_t* ptr_size_out); +/* Whether `path` is a compilable source file: a path some registered frontend + * claims (via the canonical extension registry, case-insensitively), excluding + * C headers. The C frontend registers ".h" so language-for-path can identify + * headers, but a header is not a translation unit to compile. The single + * source of truth for source classification shared by cc/build/run/dbg, so + * adding a frontend extension reaches every tool at once. */ +int driver_path_is_source(const char* path); + typedef struct DriverTargetFeatures { DriverEnv* env; KitTargetFeature* features; diff --git a/driver/lib/inputs.c b/driver/lib/inputs.c @@ -59,11 +59,27 @@ static int inputs_record_stdin(DriverInputs* in) { return 1; } +/* A compilable source file: a path some registered frontend claims (via the + * canonical extension registry, case-insensitively), excluding C headers — the + * C frontend registers ".h" so language-for-path can identify headers, but a + * header is not a translation unit to compile. No compiler exists at arg-parse + * time, so this resolves against the compile-time default frontend set + * (kit_language_for_path(NULL, ...)). The single source of truth shared by + * cc/build/run/dbg source classification. */ +int driver_path_is_source(const char* path) { + if (!path) return 0; + if (kit_language_for_path(NULL, path) == KIT_LANG_UNKNOWN) return 0; + if (driver_has_suffix(path, ".h") || driver_has_suffix(path, ".H")) return 0; + return 1; +} + int driver_inputs_classify(DriverInputs* in, const char* arg) { if (driver_streq(arg, "-")) return inputs_record_stdin(in); - if (driver_has_suffix(arg, ".c") || driver_has_suffix(arg, ".toy") || - driver_has_suffix(arg, ".s") || driver_has_suffix(arg, ".wat") || - driver_has_suffix(arg, ".wasm")) { + /* Source classification flows through the shared driver_path_is_source helper + * (canonical extension registry, headers excluded). This also fixes a latent + * bug: the former explicit list checked only ".s" case-sensitively, so ".S" + * assembly was misclassified; the registry matches case-insensitively. */ + if (driver_path_is_source(arg)) { in->sources[in->nsources++] = arg; return 1; } diff --git a/include/kit/compile.h b/include/kit/compile.h @@ -193,25 +193,45 @@ typedef struct KitFrontendVTable { KitFrontendFreeOptionsFn free_options; } KitFrontendVTable; +/* The four language resolvers below (kit_language_for_path, + * kit_language_for_name, kit_language_name, kit_language_default_extension) all + * accept a NULL compiler: with a non-NULL KitCompiler they consult that + * compiler's per-instance frontend table (honoring kit_register_frontend + * overrides); with NULL they fall back to the compile-time default frontend set + * (the KIT_LANG_*_ENABLED build configuration), so a driver can resolve + * languages at CLI arg-parse time before any KitCompiler exists. */ + /* Map a path to a language purely by its registered extension. Walks every * registered frontend's `extensions` list (case-insensitively) and returns * the owning KitLanguage. No frontend is privileged: a path whose extension * is unclaimed (or that has no extension) returns KIT_LANG_UNKNOWN rather - * than defaulting to any particular language. */ + * than defaulting to any particular language. Accepts a NULL compiler (see + * note above). */ KIT_API KitLanguage kit_language_for_path(KitCompiler*, const char* path); /* Map a canonical name or alias (a `-x` spelling such as "c", "asm", "wat") to * a language by walking every registered frontend's `names` list. Matching is * case-sensitive, mirroring the driver's exact `-x` spellings. An unclaimed - * name (or NULL) returns KIT_LANG_UNKNOWN; no frontend is privileged. */ + * name (or NULL) returns KIT_LANG_UNKNOWN; no frontend is privileged. Accepts + * a NULL compiler (see note above). */ KIT_API KitLanguage kit_language_for_name(KitCompiler*, const char* name); /* Return a language's canonical name: the first entry of the registered * frontend's `names` list. Returns NULL when no frontend is registered for * `lang` or it has no name. The returned pointer is owned by the frontend's - * static name table and is valid for the compiler's lifetime. */ + * static name table and is valid for the compiler's lifetime (or the program's + * lifetime when resolved off the default registry with a NULL compiler). + * Accepts a NULL compiler (see note above). */ KIT_API const char* kit_language_name(KitCompiler*, KitLanguage); +/* Return a language's canonical (first) file extension, with no leading dot + * (e.g. "c", "s", "toy", "wat"). Returns NULL when no frontend is registered + * for `lang` or it has no extension. The returned pointer is owned by the + * frontend's static extension table and is NUL-terminated, valid for the + * compiler's lifetime (or the program's lifetime when resolved off the default + * registry with a NULL compiler). Accepts a NULL compiler (see note above). */ +KIT_API const char* kit_language_default_extension(KitCompiler*, KitLanguage); + KIT_API KitStatus kit_register_frontend(KitCompiler*, KitLanguage, const KitFrontendVTable*); diff --git a/src/api/compile.c b/src/api/compile.c @@ -6,6 +6,7 @@ #include <kit/core.h> #include <string.h> +#include "api/lang_registry.h" #include "arch/arch.h" #include "asm/asm.h" #include "core/core.h" @@ -81,6 +82,18 @@ static int ext_eq_ci(KitSlice ext, KitSlice pat) { return 1; } +/* Resolve `lang`'s frontend vtable: from the compiler's per-instance table + * when a compiler is supplied, else from the compile-time default registry so + * the language resolvers work with c==NULL (e.g. at CLI arg-parse time, before + * a KitCompiler exists). Out-of-range `lang` yields NULL via lang_registry. */ +static const KitFrontendVTable* frontend_for(KitCompiler* c, unsigned lang) { + if (c) { + if (lang >= KIT_LANG_COUNT) return NULL; + return c->frontends[lang]; + } + return lang_registry_vtable((KitLanguage)lang); +} + KitLanguage kit_language_for_path(KitCompiler* c, const char* path) { size_t len; size_t i; @@ -88,7 +101,7 @@ KitLanguage kit_language_for_path(KitCompiler* c, const char* path) { int have_ext = 0; unsigned lang; - if (!c || !path) return KIT_LANG_UNKNOWN; + if (!path) return KIT_LANG_UNKNOWN; for (len = 0; path[len]; ++len) { } /* Strip back to the last `.` after the final `/`. No dot → no @@ -107,7 +120,7 @@ KitLanguage kit_language_for_path(KitCompiler* c, const char* path) { if (!have_ext) return KIT_LANG_UNKNOWN; for (lang = 0; lang < KIT_LANG_COUNT; ++lang) { - const KitFrontendVTable* v = c->frontends[lang]; + const KitFrontendVTable* v = frontend_for(c, lang); uint32_t e; if (!v || !v->extensions) continue; for (e = 0; e < v->nextensions; ++e) { @@ -132,9 +145,9 @@ static int name_eq(const char* name, KitSlice pat) { KitLanguage kit_language_for_name(KitCompiler* c, const char* name) { unsigned lang; - if (!c || !name) return KIT_LANG_UNKNOWN; + if (!name) return KIT_LANG_UNKNOWN; for (lang = 0; lang < KIT_LANG_COUNT; ++lang) { - const KitFrontendVTable* v = c->frontends[lang]; + const KitFrontendVTable* v = frontend_for(c, lang); uint32_t n; if (!v || !v->names) continue; for (n = 0; n < v->nnames; ++n) { @@ -146,14 +159,25 @@ KitLanguage kit_language_for_name(KitCompiler* c, const char* name) { const char* kit_language_name(KitCompiler* c, KitLanguage lang) { const KitFrontendVTable* v; - if (!c || (unsigned)lang >= KIT_LANG_COUNT) return NULL; - v = c->frontends[lang]; + if ((unsigned)lang >= KIT_LANG_COUNT) return NULL; + v = frontend_for(c, (unsigned)lang); if (!v || !v->names || v->nnames == 0) return NULL; /* The first name is the canonical one. Name tables are KIT_SLICE_LIT over * string literals, so the slice's bytes are NUL-terminated just past .len. */ return v->names[0].s; } +const char* kit_language_default_extension(KitCompiler* c, KitLanguage lang) { + const KitFrontendVTable* v; + if ((unsigned)lang >= KIT_LANG_COUNT) return NULL; + v = frontend_for(c, (unsigned)lang); + if (!v || !v->extensions || v->nextensions == 0) return NULL; + /* The first extension is the canonical one. Extension tables are + * KIT_SLICE_LIT over string literals, so the slice's bytes are + * NUL-terminated just past .len. */ + return v->extensions[0].s; +} + KitStatus kit_register_frontend(KitCompiler* c, KitLanguage lang, const KitFrontendVTable* vtable) { if (!c) return KIT_INVALID; diff --git a/src/api/lang_registry.c b/src/api/lang_registry.c @@ -51,3 +51,27 @@ void lang_registry_init(KitCompiler* c) { (void)kit_register_frontend(c, KIT_LANG_WASM, &kit_wasm_frontend_vtable); #endif } + +const KitFrontendVTable* lang_registry_vtable(KitLanguage lang) { + switch (lang) { +#if KIT_LANG_ASM_ENABLED + case KIT_LANG_ASM: + return &kit_asm_frontend_vtable; +#endif +#if KIT_LANG_C_ENABLED + case KIT_LANG_C: + return &kit_c_frontend_vtable; +#endif +#if KIT_LANG_TOY_ENABLED + case KIT_LANG_TOY: + return &kit_toy_frontend_vtable; +#endif +#if KIT_LANG_WASM_ENABLED + case KIT_LANG_WASM: + return &kit_wasm_frontend_vtable; +#endif + default: + break; + } + return NULL; +} diff --git a/src/api/lang_registry.h b/src/api/lang_registry.h @@ -1,10 +1,18 @@ #ifndef KIT_INTERNAL_API_LANG_REGISTRY_H #define KIT_INTERNAL_API_LANG_REGISTRY_H +#include <kit/compile.h> #include <kit/core.h> /* Wire every KIT_LANG_*_ENABLED frontend into c->frontends[]. Called * once during compiler construction; see src/api/lang_registry.c. */ void lang_registry_init(KitCompiler* c); +/* Return the compile-time default frontend vtable for `lang` (the same + * KIT_LANG_*_ENABLED set lang_registry_init wires), or NULL for an + * unregistered or out-of-range language. This lets the public language + * resolvers answer without a KitCompiler instance (e.g. at CLI arg-parse + * time). It does not see per-compiler kit_register_frontend overrides. */ +const KitFrontendVTable* lang_registry_vtable(KitLanguage lang); + #endif diff --git a/src/link/link_jit.c b/src/link/link_jit.c @@ -267,7 +267,9 @@ static void jit_copy_input_section_bytes(LinkImage* img, static void jit_patch_tlv_descriptors(KitJit* jit) { LinkImage* img = jit->image; Compiler* c = jit->c; - if (c->target.obj != KIT_OBJ_MACHO) return; + /* TLV descriptors are the Mach-O TLS-descriptor access model; ask the format + * via the TLS-model hook rather than naming Mach-O directly. */ + if (!obj_format_tls_via_descriptor(c)) return; if (img->tls_memsz == 0) return; /* Find every LinkSymId whose interned name is __tlv_bootstrap. The @@ -769,7 +771,8 @@ void* kit_jit_tlv_resolve(KitJit* jit, void* descriptor) { u64 offset; u8* base; if (!jit || !desc) return NULL; - if (jit->c->target.obj != KIT_OBJ_MACHO || !jit->tls_ctx) return NULL; + /* Mach-O TLS-descriptor JIT bootstrap only; gate via the TLS-model hook. */ + if (!obj_format_tls_via_descriptor(jit->c) || !jit->tls_ctx) return NULL; /* Ownership check: our descriptors carry &kit_jit_tlv_thunk at +0 and this * image's tls_ctx at +8 (jit_patch_tlv_descriptors). Refuse anything else so * a foreign descriptor never becomes a wild indirect call. */