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:
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. */