kit

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

commit 32370256c3e48008e1c170caf80adeec41f9e5cc
parent c3b3b5777cd40a0ec300f0674e31c5e843be1b1a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 14:00:02 -0700

Pluggable language frontends: public registry + driver extension

Make the frontend set extensible beyond the compiled-in builtins so a
downstream binary can register its own language(s) without forking the
driver.

- KitCompiler.frontends becomes a growable array; dynamic language ids
  start at KIT_LANG_BUILTIN_COUNT. lang_registry_init now returns
  KitStatus so registration failures propagate.
- Public kit/driver.h exposes kit_driver_main_ex + KitDriverExtension:
  an embedder supplies a register_frontends hook, and driver_main
  forwards it to the polyglot tools (build-exe/-lib/-obj, run, dbg) via
  new *_ex entry points.
- Makefile `custom-bin` target links a kit binary with extra frontend
  objects (KIT_DRIVER_EXT_OBJS / KIT_DRIVER_EXT_REGISTER).
- test/rpn-lang: end-to-end example external frontend (compile, link,
  nm, run, dbg) wired in as test-rpn-lang.

Diffstat:
MMakefile | 20+++++++++++++++++++-
Mdriver/cmd/build.c | 107++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mdriver/cmd/cc.c | 2+-
Mdriver/cmd/dbg.c | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mdriver/cmd/run.c | 44++++++++++++++++++++++++++++++++++++++++----
Mdriver/driver.h | 6++++++
Mdriver/lib/inputs.c | 10+++++++---
Mdriver/lib/inputs.h | 1+
Mdriver/main.c | 111+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Minclude/kit/compile.h | 65++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Ainclude/kit/driver.h | 15+++++++++++++++
Mlang/c/c.c | 30++++++++++++++++--------------
Mlang/toy/compile.c | 29++++++++++++++---------------
Mlang/wasm/wasm.c | 28+++++++++++++++-------------
Mmk/test.mk | 4++++
Msrc/api/compile.c | 356++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/api/core.c | 16++++++++++++++++
Msrc/api/lang_registry.c | 27++++++++++++++++++++++-----
Msrc/api/lang_registry.h | 4+++-
Msrc/core/core.c | 14+++++++++++++-
Msrc/core/core.h | 4+++-
Atest/rpn-lang/caller.c | 5+++++
Atest/rpn-lang/prog.rpn | 1+
Atest/rpn-lang/rpn_lang.c | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/rpn-lang/run.sh | 46++++++++++++++++++++++++++++++++++++++++++++++
25 files changed, 979 insertions(+), 229 deletions(-)

diff --git a/Makefile b/Makefile @@ -43,12 +43,13 @@ include mk/rt.mk LIB_AR = $(BUILD_DIR)/libkit.a BIN = $(BUILD_DIR)/kit +KIT_DRIVER_CUSTOM_BIN ?= $(BUILD_DIR)/kit-custom # =========================================================================== # Main build sequence: objects -> archive -> binary. # =========================================================================== -.PHONY: all lib bin +.PHONY: all lib bin custom-bin all: lib bin rt @@ -56,6 +57,8 @@ lib: $(LIB_AR) bin: $(BIN) $(BUILD_DIR)/support/rt +custom-bin: $(KIT_DRIVER_CUSTOM_BIN) $(dir $(KIT_DRIVER_CUSTOM_BIN))support/rt + # A kit binary resolves its runtime support from <bindir>/support/rt (an # `rt` symlink in a `support` dir beside the executable). This pattern rule # installs that symlink for ANY directory ending in /support/rt, so an @@ -76,6 +79,13 @@ $(LIB_AR): $(LIB_OBJS) $(BUILD_CONFIG) $(BIN): $(DRIVER_OBJS) $(LIB_AR) $(BUILD_CONFIG) $(CC) $(HOST_LDFLAGS) -o $@ $(DRIVER_OBJS) $(LIB_AR) $(HOST_LDLIBS) +CUSTOM_DRIVER_OBJS = $(patsubst $(BUILD_DIR)/driver/%.o,$(BUILD_DIR)/custom-driver/%.o,$(DRIVER_OBJS)) +CUSTOM_DRIVER_EXT_CFLAGS = $(if $(KIT_DRIVER_EXT_REGISTER),-DKIT_DRIVER_EXT_REGISTER=$(KIT_DRIVER_EXT_REGISTER)) + +$(KIT_DRIVER_CUSTOM_BIN): $(CUSTOM_DRIVER_OBJS) $(LIB_AR) $(KIT_DRIVER_EXT_OBJS) $(BUILD_CONFIG) + @mkdir -p $(dir $@) + $(CC) $(HOST_LDFLAGS) -o $@ $(CUSTOM_DRIVER_OBJS) $(KIT_DRIVER_EXT_OBJS) $(LIB_AR) $(HOST_LDLIBS) + # --------------------------------------------------------------------------- # Compile rules, grouped by source tree: src/ (libkit) -> lang/ (frontends) -> # vendor/ -> driver/. Each tree gets its own include scope; see mk/flags.mk for @@ -153,6 +163,14 @@ $(BUILD_DIR)/driver/env/%.o: driver/env/%.c Makefile $(BUILD_CONFIG) @mkdir -p $(dir $@) $(CC) $(DRIVER_ENV_CFLAGS) $(DRIVER_ENV_OS_CFLAGS) $(DEPFLAGS) -c $< -o $@ +$(BUILD_DIR)/custom-driver/%.o: driver/%.c Makefile $(BUILD_CONFIG) + @mkdir -p $(dir $@) + $(CC) $(DRIVER_CFLAGS) $(CUSTOM_DRIVER_EXT_CFLAGS) $(DEPFLAGS) -c $< -o $@ + +$(BUILD_DIR)/custom-driver/env/%.o: driver/env/%.c Makefile $(BUILD_CONFIG) + @mkdir -p $(dir $@) + $(CC) $(DRIVER_ENV_CFLAGS) $(DRIVER_ENV_OS_CFLAGS) $(CUSTOM_DRIVER_EXT_CFLAGS) $(DEPFLAGS) -c $< -o $@ + # =========================================================================== # Runtime (libkit_rt.a): kit compiles and archives its own runtime, one # variant per target. The variant table and per-variant source/flag lists come diff --git a/driver/cmd/build.c b/driver/cmd/build.c @@ -39,7 +39,7 @@ /* Stand-in for "no -x; resolve language from the path suffix at compile time." */ -#define BUILD_LANG_AUTO ((KitLanguage)KIT_LANG_COUNT) +#define BUILD_LANG_AUTO KIT_LANG_AUTO typedef enum BuildOutputKind { BUILD_OUT_EXE, @@ -64,8 +64,7 @@ typedef struct BuildSource { /* One -X<lang> frontend flag token, scoped to a language. */ typedef struct BuildFeFlag { - uint8_t lang; /* KitLanguage */ - uint8_t pad[3]; + KitLanguage lang; char* tok; /* argv-borrowed */ } BuildFeFlag; @@ -96,6 +95,7 @@ typedef struct BuildGroup { typedef struct BuildOptions { DriverEnv* env; + KitFrontendRegistry* frontends; const char* tool; /* "build-exe" | "build-lib" | "build-obj" */ int kind; /* BuildOutputKind */ const char* driver_path; /* argv[0] */ @@ -157,11 +157,12 @@ typedef struct BuildOptions { /* small parse helpers */ /* ===================================================================== */ -static int build_lang_from_name(const char* name, KitLanguage* out) { +static int build_lang_from_name(BuildOptions* o, 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, asm/s->ASM, toy->TOY, wasm/wat->WASM. */ - KitLanguage lang = kit_language_for_name(NULL, name); + KitLanguage lang = kit_frontend_registry_language_for_name(o->frontends, name); if (lang == KIT_LANG_UNKNOWN) return 1; *out = lang; return 0; @@ -234,7 +235,10 @@ static void build_release(BuildOptions* o) { * 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_source(BuildOptions* o, const char* s) { + return kit_frontend_registry_path_kind(o->frontends, s, NULL) == + KIT_FRONTEND_PATH_SOURCE; +} /* The language forced (via -x) for sources in group `gi`: the group's own -x, * else the global baseline's, else -1 (resolve by suffix). */ @@ -278,7 +282,7 @@ static int build_classify_positional(BuildOptions* o, const char* a) { /* Otherwise a source: a recognized source suffix, or any file at all when a * language is forced in scope (so `-x toy mykernel` compiles an extensionless * or odd-suffix file). */ - if (build_is_source(a) || build_scope_forced_lang(o, o->cur_group) >= 0) { + if (build_is_source(o, a) || build_scope_forced_lang(o, o->cur_group) >= 0) { BuildSource* s = &o->sources[o->nsources]; s->path = a; s->group = o->cur_group; @@ -314,7 +318,7 @@ static int build_try_scopable(BuildOptions* o, BuildGroup* g, int argc, driver_errf(o->tool, "-x requires an argument before `--`"); return -1; } - if (build_lang_from_name(argv[*i], &lang) != 0) { + if (build_lang_from_name(o, argv[*i], &lang) != 0) { driver_errf(o->tool, "unsupported -x language: %.*s", KIT_SLICE_ARG(kit_slice_cstr(argv[*i]))); return -1; @@ -325,8 +329,8 @@ static int build_try_scopable(BuildOptions* o, BuildGroup* g, int argc, if (driver_strneq(a, "-X", 2) && a[2] != '\0') { KitLanguage lang; BuildFeFlag* f; - if (build_lang_from_name(a + 2, &lang) != 0) { - driver_errf(o->tool, "unsupported -X language: %.*s (use c|asm|toy|wasm)", + if (build_lang_from_name(o, a + 2, &lang) != 0) { + driver_errf(o->tool, "unsupported -X language: %.*s", KIT_SLICE_ARG(kit_slice_cstr(a + 2))); return -1; } @@ -341,7 +345,7 @@ static int build_try_scopable(BuildOptions* o, BuildGroup* g, int argc, return -1; } f = &g->fe[g->nfe++]; - f->lang = (uint8_t)lang; + f->lang = lang; f->tok = argv[*i]; return 1; } @@ -1070,10 +1074,10 @@ static int build_collect_fe_argv(BuildOptions* o, uint32_t si, KitLanguage lang, uint32_t n = 0, k, p = 0; char** argv; for (k = 0; k < o->groups[0].nfe; ++k) - if (o->groups[0].fe[k].lang == (uint8_t)lang) n++; + if (o->groups[0].fe[k].lang == lang) n++; if (gi != 0) for (k = 0; k < o->groups[gi].nfe; ++k) - if (o->groups[gi].fe[k].lang == (uint8_t)lang) n++; + if (o->groups[gi].fe[k].lang == lang) n++; *out_argv = NULL; *out_n = 0; if (n == 0) return 0; @@ -1083,11 +1087,11 @@ static int build_collect_fe_argv(BuildOptions* o, uint32_t si, KitLanguage lang, return 1; } for (k = 0; k < o->groups[0].nfe; ++k) - if (o->groups[0].fe[k].lang == (uint8_t)lang) + if (o->groups[0].fe[k].lang == lang) argv[p++] = o->groups[0].fe[k].tok; if (gi != 0) for (k = 0; k < o->groups[gi].nfe; ++k) - if (o->groups[gi].fe[k].lang == (uint8_t)lang) + if (o->groups[gi].fe[k].lang == lang) argv[p++] = o->groups[gi].fe[k].tok; *out_argv = argv; *out_n = n; @@ -2145,7 +2149,8 @@ static int build_apply_env(BuildOptions* o) { return 0; } -static int build_main(int argc, char** argv, int kind, const char* tool) { +static int build_main(int argc, char** argv, int kind, const char* tool, + const KitDriverExtension* ext) { DriverEnv env; BuildOptions o = {0}; DriverRuntimeSupport runtime = {0}; @@ -2159,11 +2164,21 @@ static int build_main(int argc, char** argv, int kind, const char* tool) { int rc = 2; driver_env_init(&env); + ctx = driver_env_to_context(&env); o.env = &env; o.tool = tool; o.kind = kind; o.driver_path = argv[0]; + if (kit_frontend_registry_new(&ctx, &o.frontends) != KIT_OK || + kit_frontend_registry_add_builtin(o.frontends) != KIT_OK || + (ext && ext->register_frontends && + ext->register_frontends(o.frontends) != KIT_OK)) { + driver_errf(tool, "failed to initialize frontend registry"); + rc = 1; + goto done; + } + if (build_alloc(&o, argc) != 0) { rc = 2; goto done; @@ -2242,14 +2257,23 @@ static int build_main(int argc, char** argv, int kind, const char* tool) { } } - ctx = driver_env_to_context(&env); if (driver_target_new(&ctx, o.target, &o.target_features, tool, &target) != - KIT_OK || - driver_compiler_new(target, &ctx, &compiler) != KIT_OK) { + KIT_OK) { driver_errf(tool, "failed to initialize compiler"); rc = 1; goto done; } + { + KitCompilerOptions copts; + memset(&copts, 0, sizeof copts); + copts.frontends = o.frontends; + if (kit_compiler_new_ex(target, &ctx, &copts, &compiler) != KIT_OK) { + driver_errf(tool, "failed to initialize compiler"); + rc = 1; + goto done; + } + driver_diag_set_compiler(compiler); + } build_fill_code(&o, &code); diag.warnings_are_errors = o.warnings_are_errors ? true : false; @@ -2285,6 +2309,7 @@ done: kit_target_free(target); if (runtime_resolved) driver_runtime_support_fini(&env, &runtime); build_release(&o); + if (o.frontends) kit_frontend_registry_free(o.frontends); driver_env_fini(&env); return rc; } @@ -2303,9 +2328,9 @@ void driver_help_build_exe(void) { " kit build-exe [options] inputs...\n" "\n" "DESCRIPTION\n" - " Compiles C / asm / toy / wasm sources (language per file) in\n" - " memory and links them with any .o/.a/.so inputs into one\n" - " executable. No intermediate files.\n" + " Compiles registered source languages (language per file) in memory\n" + " and links them with any .o/.a/.so inputs into one executable. No\n" + " intermediate files.\n" "\n" "OPTIONS (selection)\n" " -o PATH Output (default a.out / a.exe)\n" @@ -2317,8 +2342,7 @@ void driver_help_build_exe(void) { " -e SYM -T script.ld Entry symbol / linker script\n" " -Wl,... Linker pass-through\n" " --group [flags] -- sources... Scope compile flags to sources\n" - " -X<lang> FLAG Per-language frontend flag " - "(c|asm|toy|wasm)\n" + " -X<lang> FLAG Per-language frontend flag\n" " -h, --help Show this help\n"))); } @@ -2344,8 +2368,7 @@ void driver_help_build_lib(void) { " -flto Link-time optimization for source inputs\n" " -target TRIPLE Cross-compile target\n" " --group [flags] -- sources... Scope compile flags to sources\n" - " -X<lang> FLAG Per-language frontend flag " - "(c|asm|toy|wasm)\n" + " -X<lang> FLAG Per-language frontend flag\n" " -h, --help Show this help\n"))); } @@ -2359,8 +2382,7 @@ void driver_help_build_obj(void) { " kit build-obj [options] sources...\n" "\n" "DESCRIPTION\n" - " Compiles each source (C / asm / toy / wasm by suffix or -x) to " - "an\n" + " Compiles each source (registered language by suffix or -x) to an\n" " object. Multiple sources with --emit=obj combine into one\n" " relocatable object (ld -r). The kit-native replacement for the\n" " retired `compile` tool.\n" @@ -2376,34 +2398,45 @@ void driver_help_build_obj(void) { "obj\n" " -target TRIPLE Cross-compile target\n" " -I/-isystem/-D/-U Preprocessor flags (C/asm frontends)\n" - " -x LANG Force language: c | asm | toy | wasm\n" + " -x LANG Force a registered language\n" " --group [flags] -- sources... Scope compile flags to sources\n" - " -X<lang> FLAG Per-language frontend flag " - "(c|asm|toy|wasm)\n" + " -X<lang> FLAG Per-language frontend flag\n" " -o - Write the emit to stdout\n" " -h, --help Show this help\n"))); } -int driver_build_exe(int argc, char** argv) { +int driver_build_exe_ex(int argc, char** argv, const KitDriverExtension* ext) { if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { driver_help_build_exe(); return 0; } - return build_main(argc, argv, BUILD_OUT_EXE, "build-exe"); + return build_main(argc, argv, BUILD_OUT_EXE, "build-exe", ext); } -int driver_build_lib(int argc, char** argv) { +int driver_build_lib_ex(int argc, char** argv, const KitDriverExtension* ext) { if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { driver_help_build_lib(); return 0; } - return build_main(argc, argv, BUILD_OUT_LIB, "build-lib"); + return build_main(argc, argv, BUILD_OUT_LIB, "build-lib", ext); } -int driver_build_obj(int argc, char** argv) { +int driver_build_obj_ex(int argc, char** argv, const KitDriverExtension* ext) { if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { driver_help_build_obj(); return 0; } - return build_main(argc, argv, BUILD_OUT_OBJ, "build-obj"); + return build_main(argc, argv, BUILD_OUT_OBJ, "build-obj", ext); +} + +int driver_build_exe(int argc, char** argv) { + return driver_build_exe_ex(argc, argv, NULL); +} + +int driver_build_lib(int argc, char** argv) { + return driver_build_lib_ex(argc, argv, NULL); +} + +int driver_build_obj(int argc, char** argv) { + return driver_build_obj_ex(argc, argv, NULL); } diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c @@ -487,7 +487,7 @@ static int cc_record_stdin(CcOptions* o, int forced_lang) { /* Stored in source_langs[] during arg parsing to mean "no -x override — * resolve from the path at compile time, once a compiler is around to * consult its frontend extension registry." */ -#define CC_LANG_AUTO ((KitLanguage)KIT_LANG_COUNT) +#define CC_LANG_AUTO KIT_LANG_AUTO static KitLanguage cc_resolve_lang(KitCompiler* c, const char* path, KitLanguage stored) { diff --git a/driver/cmd/dbg.c b/driver/cmd/dbg.c @@ -57,6 +57,7 @@ typedef struct { typedef struct DbgOpts { DriverEnv* env; + KitFrontendRegistry* frontends; size_t argv_bound; int opt_level; @@ -189,6 +190,7 @@ static int dbg_alloc_arrays(DbgOpts* o, int argc) { return 1; } if (driver_inputs_init(&o->inputs, o->env, DBG_TOOL, argc) != 0) return 1; + o->inputs.frontends = o->frontends; if (driver_cflags_init(&o->cf, o->env, argc) != 0) { driver_errf(DBG_TOOL, "out of memory"); return 1; @@ -196,21 +198,19 @@ static int dbg_alloc_arrays(DbgOpts* o, int argc) { return 0; } -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. */ +static int dbg_parse_language_name(DbgOpts* o, const char* name, + KitLanguage* out) { KitLanguage lang; if (!name || !*name || !out) return 0; - lang = kit_language_for_name(NULL, name); + lang = kit_frontend_registry_language_for_name(o->frontends, name); if (lang == KIT_LANG_UNKNOWN) return 0; *out = lang; return 1; } static int dbg_set_default_language(DbgOpts* o, const char* name) { - KitLanguage lang = KIT_LANG_COUNT; - if (!dbg_parse_language_name(name, &lang)) { + KitLanguage lang = KIT_LANG_AUTO; + if (!dbg_parse_language_name(o, name, &lang)) { driver_errf(DBG_TOOL, "unsupported language: %.*s", KIT_SLICE_ARG(kit_slice_cstr(name))); return 1; @@ -369,6 +369,7 @@ static void dbg_options_release(DbgOpts* o) { if (o->script_entries) driver_free(o->env, o->script_entries, (size_t)o->script_entries_cap * sizeof(DbgScriptEntry)); + if (o->frontends) kit_frontend_registry_free(o->frontends); } /* Compile every C source through a compiler owned by the caller and JIT-link @@ -458,7 +459,9 @@ typedef struct DbgState { /* 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]; + KitCompileSession** compile_sessions; + uint32_t ncompile_sessions; + uint32_t compile_sessions_cap; DbgSource* sources; uint32_t nsources; uint32_t sources_cap; @@ -655,10 +658,18 @@ static void dbg_bps_release_all(DbgState* s) { static void dbg_compile_sessions_release(DbgState* s) { uint32_t i; - for (i = 0; i < (uint32_t)KIT_LANG_COUNT; ++i) { + for (i = 0; i < s->ncompile_sessions; ++i) { kit_compile_session_free(s->compile_sessions[i]); s->compile_sessions[i] = NULL; } + if (s->compile_sessions) { + driver_free(s->env, s->compile_sessions, + (size_t)s->compile_sessions_cap * + sizeof(*s->compile_sessions)); + s->compile_sessions = NULL; + } + s->ncompile_sessions = 0; + s->compile_sessions_cap = 0; } static void dbg_sources_release_all(DbgState* s) { @@ -1610,13 +1621,51 @@ static KitLanguage dbg_jit_language_for_tag(DbgState* s, const char* tag, return kit_language_for_path(s->compiler, tag); } +static KitStatus dbg_compile_sessions_reserve(DbgState* s, KitLanguage lang) { + KitCompileSession** nb; + uint32_t want; + uint32_t ncap; + uint32_t i; + size_t old_sz; + size_t new_sz; + + if (!s || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO) + return KIT_INVALID; + if (lang == UINT32_MAX) return KIT_INVALID; + want = lang + 1u; + if (want <= s->compile_sessions_cap) return KIT_OK; + ncap = s->compile_sessions_cap ? s->compile_sessions_cap : 8u; + while (ncap < want) { + if (ncap > UINT32_MAX / 2u) { + ncap = want; + break; + } + ncap *= 2u; + } + if ((size_t)ncap > SIZE_MAX / sizeof(*s->compile_sessions)) + return KIT_NOMEM; + old_sz = (size_t)s->compile_sessions_cap * sizeof(*s->compile_sessions); + new_sz = (size_t)ncap * sizeof(*s->compile_sessions); + nb = (KitCompileSession**)s->env->heap->realloc( + s->env->heap, s->compile_sessions, old_sz, new_sz, + _Alignof(KitCompileSession*)); + if (!nb) return KIT_NOMEM; + for (i = s->compile_sessions_cap; i < ncap; ++i) nb[i] = NULL; + s->compile_sessions = nb; + s->compile_sessions_cap = ncap; + return KIT_OK; +} + static KitStatus dbg_compile_session_for(DbgState* s, KitLanguage lang, KitCompileSession** out) { KitCompileSessionOptions sopts; KitStatus st; - if (!s || !out || (unsigned)lang >= KIT_LANG_COUNT) return KIT_INVALID; + if (!s || !out || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO) + return KIT_INVALID; *out = NULL; + st = dbg_compile_sessions_reserve(s, lang); + if (st != KIT_OK) return st; if (s->compile_sessions[lang]) { *out = s->compile_sessions[lang]; return KIT_OK; @@ -1631,6 +1680,7 @@ static KitStatus dbg_compile_session_for(DbgState* s, KitLanguage lang, sopts.compile.preprocess = s->pp; st = kit_compile_session_new(s->compiler, &sopts, &s->compile_sessions[lang]); if (st != KIT_OK) return st; + if (s->ncompile_sessions < lang + 1u) s->ncompile_sessions = lang + 1u; *out = s->compile_sessions[lang]; return KIT_OK; } @@ -1873,6 +1923,10 @@ static void dbg_cmd_language(DbgState* s, const char* rest) { while (p[n] && !dbg_isspace((unsigned char)p[n])) ++n; if (n == 0) { const KitPreprocessOptions* pp = &s->pp; + KitCompileSession* cached = + s->default_jit_lang < s->ncompile_sessions + ? s->compile_sessions[s->default_jit_lang] + : NULL; driver_printf("Language: %.*s\n", KIT_SLICE_ARG(kit_slice_cstr(dbg_jit_language_name( s->compiler, s->default_jit_lang)))); @@ -1884,9 +1938,7 @@ static void dbg_cmd_language(DbgState* s, const char* rest) { KIT_SLICE_ARG(kit_slice_cstr(s->copts.code.debug_info ? "on" : "off")), (unsigned)pp->ninclude_dirs, (unsigned)pp->nsystem_include_dirs, (unsigned)pp->ndefines, (unsigned)pp->nundefines, - KIT_SLICE_ARG(kit_slice_cstr(s->compile_sessions[s->default_jit_lang] - ? "cached" - : "not-created"))); + KIT_SLICE_ARG(kit_slice_cstr(cached ? "cached" : "not-created"))); return; } if (n >= sizeof(tmp)) { @@ -1899,7 +1951,7 @@ static void dbg_cmd_language(DbgState* s, const char* rest) { * recomputed below from dbg_jit_default_name, so no name buffer is requested. */ lang = dbg_jit_language_for_tag(s, tmp, NULL, 0, NULL); - if (lang == KIT_LANG_COUNT) { + if (lang == KIT_LANG_UNKNOWN) { dbg_errf(s, "unsupported language: %.*s", KIT_SLICE_ARG(kit_slice_cstr(tmp))); return; @@ -3177,7 +3229,19 @@ static void dbg_repl(DbgState* s) { * Top-level entry * ============================================================ */ -int driver_dbg(int argc, char** argv) { +static int dbg_init_frontends(DbgOpts* o, const KitContext* ctx, + const KitDriverExtension* ext) { + if (kit_frontend_registry_new(ctx, &o->frontends) != KIT_OK || + kit_frontend_registry_add_builtin(o->frontends) != KIT_OK || + (ext && ext->register_frontends && + ext->register_frontends(o->frontends) != KIT_OK)) { + driver_errf(DBG_TOOL, "failed to initialize frontend registry"); + return 1; + } + return 0; +} + +int driver_dbg_ex(int argc, char** argv, const KitDriverExtension* ext) { DriverEnv env; DbgOpts o = {0}; KitCompiler* compiler = NULL; @@ -3196,6 +3260,13 @@ int driver_dbg(int argc, char** argv) { driver_env_init(&env); o.env = &env; + ctx = driver_env_to_context(&env); + + if (dbg_init_frontends(&o, &ctx, ext) != 0) { + dbg_options_release(&o); + driver_env_fini(&env); + return 1; + } if (dbg_parse(argc, argv, &o) != 0) { dbg_options_release(&o); @@ -3203,21 +3274,32 @@ int driver_dbg(int argc, char** argv) { return 2; } - ctx = driver_env_to_context(&env); jhost = driver_env_to_jit_host(&env); dhost = driver_env_to_dbg_host(&env); { KitTargetOptions topts; memset(&topts, 0, sizeof topts); topts.spec = driver_host_target(); - if (kit_target_new(&ctx, &topts, &target) != KIT_OK || - driver_compiler_new(target, &ctx, &compiler) != KIT_OK) { + if (kit_target_new(&ctx, &topts, &target) != KIT_OK) { driver_errf(DBG_TOOL, "failed to initialize compiler"); kit_target_free(target); dbg_options_release(&o); driver_env_fini(&env); return 1; } + { + KitCompilerOptions copts; + memset(&copts, 0, sizeof copts); + copts.frontends = o.frontends; + if (kit_compiler_new_ex(target, &ctx, &copts, &compiler) != KIT_OK) { + driver_errf(DBG_TOOL, "failed to initialize compiler"); + kit_target_free(target); + dbg_options_release(&o); + driver_env_fini(&env); + return 1; + } + driver_diag_set_compiler(compiler); + } } rc = dbg_compile_and_jit(&o, compiler, &jhost, &jit); @@ -3292,3 +3374,5 @@ int driver_dbg(int argc, char** argv) { driver_env_fini(&env); return (st.batch_mode && st.error_count > 0) ? 1 : 0; } + +int driver_dbg(int argc, char** argv) { return driver_dbg_ex(argc, argv, NULL); } diff --git a/driver/cmd/run.c b/driver/cmd/run.c @@ -30,6 +30,7 @@ typedef struct RunOptions { DriverEnv* env; + KitFrontendRegistry* frontends; size_t argv_bound; int opt_level; @@ -293,6 +294,7 @@ static int run_alloc_arrays(RunOptions* o, int argc) { return 1; } if (driver_inputs_init(&o->inputs, o->env, RUN_TOOL, argc) != 0) return 1; + o->inputs.frontends = o->frontends; if (driver_cflags_init( &o->cf, o->env, argc + DRIVER_HOSTED_MAX_INCLUDES + DRIVER_HOSTED_MAX_DEFINES) != 0) { @@ -673,6 +675,7 @@ static void run_options_release(RunOptions* o) { driver_target_features_fini(&o->target_features, o->env); driver_cflags_fini(&o->cf, o->env); driver_free(o->env, o->prog_argv, bound * sizeof(*o->prog_argv)); + if (o->frontends) kit_frontend_registry_free(o->frontends); } static void run_fill_compile_opts(const RunOptions* o, @@ -817,7 +820,19 @@ static void* interp_jit_resolve_tls(void* ctx, KitSlice name, int64_t addend) { return tls ? (uint8_t*)tls + addend : NULL; } -int driver_run(int argc, char** argv) { +static int run_init_frontends(RunOptions* o, const KitContext* ctx, + const KitDriverExtension* ext) { + if (kit_frontend_registry_new(ctx, &o->frontends) != KIT_OK || + kit_frontend_registry_add_builtin(o->frontends) != KIT_OK || + (ext && ext->register_frontends && + ext->register_frontends(o->frontends) != KIT_OK)) { + driver_errf(RUN_TOOL, "failed to initialize frontend registry"); + return 1; + } + return 0; +} + +int driver_run_ex(int argc, char** argv, const KitDriverExtension* ext) { DriverEnv env; RunOptions ro = {0}; KitContext ctx; @@ -843,6 +858,13 @@ int driver_run(int argc, char** argv) { driver_env_init(&env); ro.env = &env; + ctx = driver_env_to_context(&env); + + if (run_init_frontends(&ro, &ctx, ext) != 0) { + run_options_release(&ro); + driver_env_fini(&env); + return 1; + } if (run_parse(argc, argv, &ro) != 0) { run_options_release(&ro); @@ -867,11 +889,9 @@ int driver_run(int argc, char** argv) { /* Compiler backs the JIT image — keep it alive across kit_jit_lookup * and the entry call, free after kit_jit_free. */ - ctx = driver_env_to_context(&env); jhost = driver_env_to_jit_host(&env); if (driver_target_new(&ctx, ro.target, &ro.target_features, RUN_TOOL, - &target) != KIT_OK || - driver_compiler_new(target, &ctx, &compiler) != KIT_OK) { + &target) != KIT_OK) { driver_errf(RUN_TOOL, "failed to initialize compiler"); kit_target_free(target); run_metrics_finish(metrics); @@ -879,6 +899,20 @@ int driver_run(int argc, char** argv) { driver_env_fini(&env); return 1; } + { + KitCompilerOptions copts; + memset(&copts, 0, sizeof copts); + copts.frontends = ro.frontends; + if (kit_compiler_new_ex(target, &ctx, &copts, &compiler) != KIT_OK) { + driver_errf(RUN_TOOL, "failed to initialize compiler"); + kit_target_free(target); + run_metrics_finish(metrics); + run_options_release(&ro); + driver_env_fini(&env); + return 1; + } + driver_diag_set_compiler(compiler); + } /* For --no-jit, attach an InterpProgram so the optimizer captures each * function's IR as it compiles. The native object/JIT image is still built @@ -1045,3 +1079,5 @@ after_entry: driver_env_fini(&env); return rc; } + +int driver_run(int argc, char** argv) { return driver_run_ex(argc, argv, NULL); } diff --git a/driver/driver.h b/driver/driver.h @@ -4,6 +4,7 @@ #include <kit/compile.h> #include <kit/core.h> #include <kit/dbg.h> +#include <kit/driver.h> #include <kit/jit.h> #include "env.h" @@ -24,6 +25,9 @@ int driver_check(int argc, char** argv); int driver_build_exe(int argc, char** argv); int driver_build_lib(int argc, char** argv); int driver_build_obj(int argc, char** argv); +int driver_build_exe_ex(int argc, char** argv, const KitDriverExtension*); +int driver_build_lib_ex(int argc, char** argv, const KitDriverExtension*); +int driver_build_obj_ex(int argc, char** argv, const KitDriverExtension*); int driver_install(int argc, char** argv); int driver_cpp(int argc, char** argv); int driver_as(int argc, char** argv); @@ -36,6 +40,8 @@ int driver_image(int argc, char** argv); int driver_objdump(int argc, char** argv); int driver_dbg(int argc, char** argv); int driver_run(int argc, char** argv); +int driver_dbg_ex(int argc, char** argv, const KitDriverExtension*); +int driver_run_ex(int argc, char** argv, const KitDriverExtension*); int driver_emu(int argc, char** argv); int driver_nm(int argc, char** argv); int driver_size(int argc, char** argv); diff --git a/driver/lib/inputs.c b/driver/lib/inputs.c @@ -69,9 +69,7 @@ static int inputs_record_stdin(DriverInputs* in) { * 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; + return kit_language_path_kind(NULL, path, NULL) == KIT_FRONTEND_PATH_SOURCE; } int driver_inputs_classify(DriverInputs* in, const char* arg) { @@ -80,6 +78,12 @@ int driver_inputs_classify(DriverInputs* in, const char* arg) { * via the canonical case-insensitive extension registry (headers excluded), * then the object/archive suffix tests. A .so/.dylib is KIT_INPUT_DSO, which * run/dbg do not accept, so it falls through to "unrecognized" like before. */ + if (in->frontends && + kit_frontend_registry_path_kind(in->frontends, arg, NULL) == + KIT_FRONTEND_PATH_SOURCE) { + in->sources[in->nsources++] = arg; + return 1; + } switch (kit_input_kind_for_path(NULL, arg)) { case KIT_INPUT_SOURCE: in->sources[in->nsources++] = arg; diff --git a/driver/lib/inputs.h b/driver/lib/inputs.h @@ -31,6 +31,7 @@ typedef struct DriverInputs { DriverEnv* env; const char* tool; + const KitFrontendRegistry* frontends; /* borrowed; NULL means builtins */ size_t bound; const char** sources; /* .c .cc .cpp paths (borrowed) */ diff --git a/driver/main.c b/driver/main.c @@ -11,11 +11,14 @@ */ typedef int (*DriverToolMain)(int argc, char** argv); +typedef int (*DriverToolMainEx)(int argc, char** argv, + const KitDriverExtension*); typedef void (*DriverToolHelp)(void); typedef struct DriverToolDesc { const char* name; DriverToolMain main; + DriverToolMainEx main_ex; DriverToolHelp help; const char* summary; /* DriverToolGroup bits; 0 => not in the default install set */ @@ -24,167 +27,167 @@ typedef struct DriverToolDesc { static const DriverToolDesc driver_tools[] = { #if KIT_TOOL_CC_ENABLED - {"cc", driver_cc, driver_help_cc, + {"cc", driver_cc, NULL, driver_help_cc, "Compile (and link) C sources, with cpp / dep-emit / -shared modes", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_CHECK_ENABLED - {"check", driver_check, driver_help_check, + {"check", driver_check, NULL, driver_help_check, "Run C frontend checks without emitting code", DRIVER_GROUP_OTHER}, #endif #if KIT_TOOL_BUILD_EXE_ENABLED - {"build-exe", driver_build_exe, driver_help_build_exe, + {"build-exe", driver_build_exe, driver_build_exe_ex, driver_help_build_exe, "Compile a polyglot source set and link an executable (in memory)", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_BUILD_LIB_ENABLED - {"build-lib", driver_build_lib, driver_help_build_lib, + {"build-lib", driver_build_lib, driver_build_lib_ex, driver_help_build_lib, "Compile sources into a static .a or shared library (in memory)", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_BUILD_OBJ_ENABLED - {"build-obj", driver_build_obj, driver_help_build_obj, + {"build-obj", driver_build_obj, driver_build_obj_ex, driver_help_build_obj, "Compile sources to an object / asm / C / IR, or check (replaces compile)", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_INSTALL_ENABLED - {"install", driver_install, driver_help_install, + {"install", driver_install, NULL, driver_help_install, "Symlink the kit tools into a dir for drop-in toolchain use", DRIVER_GROUP_OTHER}, #endif #if KIT_TOOL_CPP_ENABLED - {"cpp", driver_cpp, driver_help_cpp, + {"cpp", driver_cpp, NULL, driver_help_cpp, "Standalone C preprocessor (alias for `cc -E` minus link scaffold)", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_AS_ENABLED - {"as", driver_as, driver_help_as, + {"as", driver_as, NULL, driver_help_as, "Assemble a GAS-subset text source into a relocatable object", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_LD_ENABLED - {"ld", driver_ld, driver_help_ld, + {"ld", driver_ld, NULL, driver_help_ld, "Link objects/archives into an executable or shared library", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_AR_ENABLED - {"ar", driver_ar, driver_help_ar, + {"ar", driver_ar, NULL, driver_help_ar, "Create / modify / list / extract POSIX `ar` archives", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_RANLIB_ENABLED - {"ranlib", driver_ranlib, driver_help_ranlib, + {"ranlib", driver_ranlib, NULL, driver_help_ranlib, "Refresh the symbol index of an `ar` archive", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_STRIP_ENABLED - {"strip", driver_strip, driver_help_strip, + {"strip", driver_strip, NULL, driver_help_strip, "Drop debug sections and/or symbols from a .o or .a", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_OBJCOPY_ENABLED - {"objcopy", driver_objcopy, driver_help_objcopy, + {"objcopy", driver_objcopy, NULL, driver_help_objcopy, "Copy and transform an object file (rename / remove / format)", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_IMAGE_ENABLED - {"image", driver_image, driver_help_image, + {"image", driver_image, NULL, driver_help_image, "Emit flat kernel/load images from linked objects", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_OBJDUMP_ENABLED - {"objdump", driver_objdump, driver_help_objdump, + {"objdump", driver_objdump, NULL, driver_help_objdump, "Dump sections, symbols, disassembly, hex, and relocations", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_RUN_ENABLED - {"run", driver_run, driver_help_run, + {"run", driver_run, driver_run_ex, driver_help_run, "JIT-compile inputs and invoke the entry symbol in-process", DRIVER_GROUP_OTHER}, #endif #if KIT_TOOL_DBG_ENABLED - {"dbg", driver_dbg, driver_help_dbg, + {"dbg", driver_dbg, driver_dbg_ex, driver_help_dbg, "Interactive JIT debugger (REPL on top of the JIT image)", DRIVER_GROUP_OTHER}, #endif #if KIT_TOOL_EMU_ENABLED - {"emu", driver_emu, driver_help_emu, + {"emu", driver_emu, NULL, driver_help_emu, "Run a guest user-mode ELF (aarch64/riscv64) on the host", DRIVER_GROUP_OTHER}, #endif #if KIT_TOOL_NM_ENABLED - {"nm", driver_nm, driver_help_nm, "List symbols from object files", + {"nm", driver_nm, NULL, driver_help_nm, "List symbols from object files", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_SIZE_ENABLED - {"size", driver_size, driver_help_size, + {"size", driver_size, NULL, driver_help_size, "Display section sizes of object files", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_ADDR2LINE_ENABLED - {"addr2line", driver_addr2line, driver_help_addr2line, + {"addr2line", driver_addr2line, NULL, driver_help_addr2line, "Translate addresses to file:line using debug info", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_SYMBOLIZE_ENABLED - {"symbolize", driver_symbolize, driver_help_symbolize, + {"symbolize", driver_symbolize, NULL, driver_help_symbolize, "Annotate a kit backtrace stream with func at file:line", DRIVER_GROUP_OTHER}, #endif #if KIT_TOOL_STRINGS_ENABLED - {"strings", driver_strings, driver_help_strings, + {"strings", driver_strings, NULL, driver_help_strings, "Print printable character sequences found in a file", DRIVER_GROUP_TOOLCHAIN}, #endif #if KIT_TOOL_CAS_ENABLED - {"cas", driver_cas, driver_help_cas, + {"cas", driver_cas, NULL, driver_help_cas, "Store, inspect, verify, and materialize kit CAS blobs and trees", DRIVER_GROUP_OTHER}, #endif #if KIT_TOOL_PKG_ENABLED - {"pkg", driver_pkg, driver_help_pkg, + {"pkg", driver_pkg, NULL, driver_help_pkg, "Bundle, sign, verify, and unpack distributable .kpkg packages", DRIVER_GROUP_OTHER}, #endif #if KIT_TOOL_XXD_ENABLED - {"xxd", driver_xxd, driver_help_xxd, + {"xxd", driver_xxd, NULL, driver_help_xxd, "Hex dump any file (and reverse a dump back to binary)", DRIVER_GROUP_BYTEUTIL}, #endif #if KIT_TOOL_CMP_ENABLED - {"cmp", driver_cmp, driver_help_cmp, "Compare two files byte by byte", + {"cmp", driver_cmp, NULL, driver_help_cmp, "Compare two files byte by byte", DRIVER_GROUP_BYTEUTIL}, #endif #if KIT_TOOL_HASH_ENABLED - {"hash", driver_hash, driver_help_hash, + {"hash", driver_hash, NULL, driver_help_hash, "Hash files with SHA-256, BLAKE2b, or CRC-32", DRIVER_GROUP_OTHER}, - {"sha256sum", driver_sha256sum, driver_help_hash, + {"sha256sum", driver_sha256sum, NULL, driver_help_hash, "SHA-256 of files or stdin (sha256sum-compatible)", DRIVER_GROUP_BYTEUTIL}, - {"b2sum", driver_b2sum, driver_help_hash, + {"b2sum", driver_b2sum, NULL, driver_help_hash, "BLAKE2b-256 of files or stdin (b2sum-style)", DRIVER_GROUP_BYTEUTIL}, - {"crc32", driver_crc32, driver_help_hash, "CRC-32 of files or stdin", + {"crc32", driver_crc32, NULL, driver_help_hash, "CRC-32 of files or stdin", DRIVER_GROUP_BYTEUTIL}, #endif #if KIT_TOOL_COMPRESS_ENABLED - {"compress", driver_compress, driver_help_compress, + {"compress", driver_compress, NULL, driver_help_compress, "Compress or decompress data (gzip, lz4 frame)", DRIVER_GROUP_OTHER}, - {"gzip", driver_gzip, driver_help_compress, + {"gzip", driver_gzip, NULL, driver_help_compress, "Compress to gzip (.gz); -d to decompress", DRIVER_GROUP_BYTEUTIL}, - {"gunzip", driver_gunzip, driver_help_compress, + {"gunzip", driver_gunzip, NULL, driver_help_compress, "Decompress gzip (.gz) streams", DRIVER_GROUP_BYTEUTIL}, - {"lz4", driver_lz4, driver_help_compress, + {"lz4", driver_lz4, NULL, driver_help_compress, "Compress to LZ4 frame (.lz4); -d to decompress", DRIVER_GROUP_BYTEUTIL}, - {"lz4c", driver_lz4c, driver_help_compress, + {"lz4c", driver_lz4c, NULL, driver_help_compress, "Compress to LZ4 frame (.lz4) (lz4 alias)", DRIVER_GROUP_BYTEUTIL}, #endif #if KIT_TOOL_DISAS_ENABLED - {"disas", driver_disas, driver_help_disas, + {"disas", driver_disas, NULL, driver_help_disas, "Disassemble raw machine-code bytes for a target arch", DRIVER_GROUP_OTHER}, #endif #if KIT_TOOL_MC_ENABLED - {"mc", driver_mc, driver_help_mc, + {"mc", driver_mc, NULL, driver_help_mc, "Assemble one instruction and show its machine-code encoding", DRIVER_GROUP_OTHER}, #endif - {NULL, NULL, NULL, NULL, DRIVER_GROUP_OTHER}, + {NULL, NULL, NULL, NULL, NULL, DRIVER_GROUP_OTHER}, }; unsigned driver_tool_count(void) { @@ -218,9 +221,13 @@ static const DriverToolDesc* find_tool(const char* name) { return NULL; } -static int dispatch(const char* name, int argc, char** argv) { +static int dispatch(const char* name, int argc, char** argv, + const KitDriverExtension* ext) { const DriverToolDesc* tool = find_tool(name); - if (tool) return tool->main(argc, argv); + if (tool) { + if (tool->main_ex) return tool->main_ex(argc, argv, ext); + return tool->main(argc, argv); + } return -1; } @@ -283,7 +290,7 @@ void driver_help_top(void) { " 2 bad command-line usage\n"); } -int driver_main(int argc, char** argv) { +int kit_driver_main_ex(int argc, char** argv, const KitDriverExtension* ext) { const char* name; int rc; @@ -292,7 +299,7 @@ int driver_main(int argc, char** argv) { /* Multi-call form first: argv[0] is the tool name (e.g. installed as * a `cc` symlink). Help inside the tool is gated by the tool itself. */ - rc = dispatch(name, argc, argv); + rc = dispatch(name, argc, argv, ext); if (rc != -1) return rc; /* From here on argv[0] was the bare "kit" binary. Apply the @@ -321,7 +328,7 @@ int driver_main(int argc, char** argv) { { char* tool_name = argv[1]; argv[1] = argv[0]; - rc = dispatch(tool_name, argc - 1, argv + 1); + rc = dispatch(tool_name, argc - 1, argv + 1, ext); argv[1] = tool_name; if (rc != -1) return rc; } @@ -332,4 +339,18 @@ int driver_main(int argc, char** argv) { return 2; } -int main(int argc, char** argv) { return driver_main(argc, argv); } +int driver_main(int argc, char** argv) { return kit_driver_main_ex(argc, argv, NULL); } + +#ifdef KIT_DRIVER_EXT_REGISTER +extern KitStatus KIT_DRIVER_EXT_REGISTER(KitFrontendRegistry*); +#endif + +int main(int argc, char** argv) { +#ifdef KIT_DRIVER_EXT_REGISTER + KitDriverExtension ext; + ext.register_frontends = KIT_DRIVER_EXT_REGISTER; + return kit_driver_main_ex(argc, argv, &ext); +#else + return kit_driver_main_ex(argc, argv, NULL); +#endif +} diff --git a/include/kit/compile.h b/include/kit/compile.h @@ -14,17 +14,20 @@ * should use kit/cg.h instead. */ -typedef enum KitLanguage { - /* No registered frontend claims the input. kit_language_for_path - * returns this when an extension matches nothing; it is never a valid - * frontend index (the compile paths reject it). */ - KIT_LANG_UNKNOWN = -1, - KIT_LANG_C = 0, - KIT_LANG_ASM = 1, - KIT_LANG_TOY = 2, - KIT_LANG_WASM = 3, - KIT_LANG_COUNT = 4, -} KitLanguage; +typedef uint32_t KitLanguage; + +/* Stable builtin language ids. External frontends registered through a + * KitFrontendRegistry receive ids starting at KIT_LANG_BUILTIN_COUNT. */ +#define KIT_LANG_C ((KitLanguage)0u) +#define KIT_LANG_ASM ((KitLanguage)1u) +#define KIT_LANG_TOY ((KitLanguage)2u) +#define KIT_LANG_WASM ((KitLanguage)3u) +#define KIT_LANG_BUILTIN_COUNT ((KitLanguage)4u) +/* Backward-compatible name for the builtin count. Do not use as an AUTO + * sentinel: dynamic frontend ids may be >= this value. */ +#define KIT_LANG_COUNT KIT_LANG_BUILTIN_COUNT +#define KIT_LANG_AUTO ((KitLanguage)0xfffffffeu) +#define KIT_LANG_UNKNOWN ((KitLanguage)0xffffffffu) typedef struct KitDiagnosticOptions { bool warnings_are_errors; @@ -95,6 +98,7 @@ typedef struct KitFrontendCompileOptions { } KitFrontendCompileOptions; typedef struct KitFrontend KitFrontend; +typedef struct KitFrontendRegistry KitFrontendRegistry; typedef struct KitFrontendState KitFrontendState; typedef struct KitCg KitCg; @@ -132,6 +136,12 @@ typedef struct KitFrontendCaps { bool cache_repl_toplevel_source; } KitFrontendCaps; +typedef enum KitFrontendPathKind { + KIT_FRONTEND_PATH_UNKNOWN = 255, + KIT_FRONTEND_PATH_SOURCE = 0, + KIT_FRONTEND_PATH_HEADER = 1, +} KitFrontendPathKind; + /* Parse the frontend-specific command-line flags a generic driver did not * itself consume, producing an opaque options blob to plant in * KitFrontendCompileOptions.language_options. The blob is allocated through the @@ -160,6 +170,11 @@ typedef struct KitFrontendVTable { * `"s"` entry. */ const KitSlice* extensions; uint32_t nextensions; + /* Optional parallel array to `extensions`. NULL means every extension is a + * compilable source. Use KIT_FRONTEND_PATH_HEADER for extensions that the + * language owns for include/import resolution but that should not be treated + * as standalone translation units by generic drivers. */ + const uint8_t* extension_kinds; /* KitFrontendPathKind per extension */ /* Counted list of canonical names and aliases (e.g. the `-x` spellings a * driver accepts) that identify this frontend's language. @@ -194,6 +209,31 @@ typedef struct KitFrontendVTable { KitFrontendFreeOptionsFn free_options; } KitFrontendVTable; +typedef struct KitCompilerOptions { + /* Optional borrowed registry. NULL selects the compiled-in builtin frontend + * set. The compiler copies the registry's vtable pointers at construction + * time, so the registry may be freed after kit_compiler_new_ex returns. */ + const KitFrontendRegistry* frontends; +} KitCompilerOptions; + +KIT_API KitStatus kit_frontend_registry_new(const KitContext*, + KitFrontendRegistry** out); +KIT_API void kit_frontend_registry_free(KitFrontendRegistry*); +KIT_API KitStatus kit_frontend_registry_add_builtin(KitFrontendRegistry*); +KIT_API KitStatus kit_frontend_registry_add(KitFrontendRegistry*, + const KitFrontendVTable*, + KitLanguage* out_lang); +KIT_API KitLanguage kit_frontend_registry_language_for_path( + const KitFrontendRegistry*, const char* path); +KIT_API KitLanguage kit_frontend_registry_language_for_name( + const KitFrontendRegistry*, const char* name); +KIT_API KitFrontendPathKind kit_frontend_registry_path_kind( + const KitFrontendRegistry*, const char* path, KitLanguage* out_lang); + +KIT_API KitStatus kit_compiler_new_ex(const KitTarget*, const KitContext*, + const KitCompilerOptions*, + KitCompiler** out); + /* 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 @@ -209,6 +249,9 @@ typedef struct KitFrontendVTable { * than defaulting to any particular language. Accepts a NULL compiler (see * note above). */ KIT_API KitLanguage kit_language_for_path(KitCompiler*, const char* path); +KIT_API KitFrontendPathKind kit_language_path_kind(KitCompiler*, + const char* path, + KitLanguage* out_lang); /* 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 diff --git a/include/kit/driver.h b/include/kit/driver.h @@ -0,0 +1,15 @@ +#ifndef KIT_DRIVER_PUBLIC_H +#define KIT_DRIVER_PUBLIC_H + +#include <kit/compile.h> + +typedef KitStatus (*KitDriverRegisterFrontendsFn)(KitFrontendRegistry*); + +typedef struct KitDriverExtension { + KitDriverRegisterFrontendsFn register_frontends; +} KitDriverExtension; + +KIT_API int kit_driver_main_ex(int argc, char** argv, + const KitDriverExtension* ext); + +#endif diff --git a/lang/c/c.c b/lang/c/c.c @@ -207,22 +207,24 @@ static void c_frontend_free(KitFrontendState* frontend) { * language-for-path lookup resolves them by the same registry walk as every * other language, with no special fallback. */ static const KitSlice c_extensions[] = {KIT_SLICE_LIT("c"), KIT_SLICE_LIT("h")}; +static const uint8_t c_extension_kinds[] = {KIT_FRONTEND_PATH_SOURCE, + KIT_FRONTEND_PATH_HEADER}; /* Canonical `-x` name; mirrors the driver's "c" spelling. */ static const KitSlice c_names[] = {KIT_SLICE_LIT("c")}; const KitFrontendVTable kit_c_frontend_vtable = { - c_frontend_new, - c_frontend_compile_cg, - NULL, /* compile_obj: semantic frontends are wrapped by compile session */ - c_frontend_free, - c_extensions, - (uint32_t)(sizeof c_extensions / sizeof c_extensions[0]), - c_names, - (uint32_t)(sizeof c_names / sizeof c_names[0]), - /* commit/abort: C has no durable cross-compile state yet */ - NULL, - NULL, - {true, KIT_FRONTEND_LTO_CG, false}, - NULL, /* parse_options: C has no frontend-specific flags */ - NULL, /* free_options */ + .new_frontend = c_frontend_new, + .compile_cg = c_frontend_compile_cg, + .compile_obj = NULL, + .free_frontend = c_frontend_free, + .extensions = c_extensions, + .nextensions = (uint32_t)(sizeof c_extensions / sizeof c_extensions[0]), + .extension_kinds = c_extension_kinds, + .names = c_names, + .nnames = (uint32_t)(sizeof c_names / sizeof c_names[0]), + .commit = NULL, + .abort = NULL, + .caps = {true, KIT_FRONTEND_LTO_CG, false}, + .parse_options = NULL, + .free_options = NULL, }; diff --git a/lang/toy/compile.c b/lang/toy/compile.c @@ -230,19 +230,18 @@ static const KitSlice toy_extensions[] = {KIT_SLICE_LIT("toy")}; static const KitSlice toy_names[] = {KIT_SLICE_LIT("toy")}; const KitFrontendVTable kit_toy_frontend_vtable = { - toy_frontend_new, - toy_frontend_compile_cg, - NULL, /* compile_obj: semantic frontends are wrapped by compile session */ - toy_frontend_free, - toy_extensions, - (uint32_t)(sizeof toy_extensions / sizeof toy_extensions[0]), - toy_names, - (uint32_t)(sizeof toy_names / sizeof toy_names[0]), - toy_frontend_commit, - toy_frontend_abort, - /* cache_repl_toplevel_source: the toy REPL re-reads earlier toplevel text. - */ - {false, KIT_FRONTEND_LTO_CG, true}, - NULL, /* parse_options: no toy-specific flags yet */ - NULL, /* free_options */ + .new_frontend = toy_frontend_new, + .compile_cg = toy_frontend_compile_cg, + .compile_obj = NULL, + .free_frontend = toy_frontend_free, + .extensions = toy_extensions, + .nextensions = (uint32_t)(sizeof toy_extensions / sizeof toy_extensions[0]), + .extension_kinds = NULL, + .names = toy_names, + .nnames = (uint32_t)(sizeof toy_names / sizeof toy_names[0]), + .commit = toy_frontend_commit, + .abort = toy_frontend_abort, + .caps = {false, KIT_FRONTEND_LTO_CG, true}, + .parse_options = NULL, + .free_options = NULL, }; diff --git a/lang/wasm/wasm.c b/lang/wasm/wasm.c @@ -157,19 +157,21 @@ static const KitSlice wasm_names[] = {KIT_SLICE_LIT("wasm"), KIT_SLICE_LIT("wat")}; const KitFrontendVTable kit_wasm_frontend_vtable = { - wasm_frontend_new, - wasm_frontend_compile_cg, - NULL, /* compile_obj: semantic frontends are wrapped by compile session */ - wasm_frontend_free, - wasm_extensions, - (uint32_t)(sizeof wasm_extensions / sizeof wasm_extensions[0]), - wasm_names, - (uint32_t)(sizeof wasm_names / sizeof wasm_names[0]), - NULL, /* commit: wasm has no durable cross-compile state */ - NULL, /* abort */ - {false, KIT_FRONTEND_LTO_CG, false}, - wasm_parse_options, - wasm_free_options, + .new_frontend = wasm_frontend_new, + .compile_cg = wasm_frontend_compile_cg, + .compile_obj = NULL, + .free_frontend = wasm_frontend_free, + .extensions = wasm_extensions, + .nextensions = + (uint32_t)(sizeof wasm_extensions / sizeof wasm_extensions[0]), + .extension_kinds = NULL, + .names = wasm_names, + .nnames = (uint32_t)(sizeof wasm_names / sizeof wasm_names[0]), + .commit = NULL, + .abort = NULL, + .caps = {false, KIT_FRONTEND_LTO_CG, false}, + .parse_options = wasm_parse_options, + .free_options = wasm_free_options, }; KIT_API int kit_wasm_wat_to_wasm(KitCompiler* c, const KitSlice* input, diff --git a/mk/test.mk b/mk/test.mk @@ -113,6 +113,7 @@ TEST_TARGETS = \ test-pp-ok \ test-pp-file-escape \ test-pp-include-next \ + test-rpn-lang \ test-rt-headers \ test-rt-runtime \ test-rt-backtrace \ @@ -220,6 +221,9 @@ test-driver-cc: bin test-driver-build: bin @KIT=$(abspath $(BIN)) sh test/buildcmds/run.sh +test-rpn-lang: lib + @KIT_BUILD_DIR=$(abspath $(BUILD_DIR)/test/rpn-lang) sh test/rpn-lang/run.sh + ifeq ($(HOST_OS),linux) test-driver-lsan: @$(MAKE) test-driver RELEASE=0 BUILD_DIR=build/lsan \ diff --git a/src/api/compile.c b/src/api/compile.c @@ -4,6 +4,7 @@ #include <kit/cg.h> #include <kit/compile.h> #include <kit/core.h> +#include <stdint.h> #include <string.h> #include "api/lang_registry.h" @@ -34,6 +35,13 @@ struct KitCompileSession { KitFrontendCompileOptions opts; }; +struct KitFrontendRegistry { + const KitContext* ctx; + const KitFrontendVTable** frontends; + uint32_t nfrontends; + uint32_t cap; +}; + static KitFrontendState* asm_frontend_new(KitCompiler* c); static KitStatus asm_frontend_compile(KitFrontendState* fe, const KitFrontendCompileOptions* opts, @@ -46,19 +54,20 @@ static const KitSlice asm_extensions[] = {KIT_SLICE_LIT("s")}; static const KitSlice asm_names[] = {KIT_SLICE_LIT("asm"), KIT_SLICE_LIT("s")}; const KitFrontendVTable kit_asm_frontend_vtable = { - asm_frontend_new, - NULL, /* compile_cg: asm participates in LTO as an opaque object */ - asm_frontend_compile, - asm_frontend_free, - asm_extensions, - (uint32_t)(sizeof asm_extensions / sizeof asm_extensions[0]), - asm_names, - (uint32_t)(sizeof asm_names / sizeof asm_names[0]), - NULL, /* commit: asm has no durable cross-compile state */ - NULL, /* abort */ - {false, KIT_FRONTEND_LTO_OPAQUE, false}, - NULL, /* parse_options: no asm-specific flags */ - NULL, /* free_options */ + .new_frontend = asm_frontend_new, + .compile_cg = NULL, /* asm participates in LTO as an opaque object */ + .compile_obj = asm_frontend_compile, + .free_frontend = asm_frontend_free, + .extensions = asm_extensions, + .nextensions = (uint32_t)(sizeof asm_extensions / sizeof asm_extensions[0]), + .extension_kinds = NULL, + .names = asm_names, + .nnames = (uint32_t)(sizeof asm_names / sizeof asm_names[0]), + .commit = NULL, + .abort = NULL, + .caps = {false, KIT_FRONTEND_LTO_OPAQUE, false}, + .parse_options = NULL, + .free_options = NULL, }; static _Noreturn void panic_bad_options(Compiler* c, const char* msg) { @@ -82,30 +91,152 @@ static int ext_eq_ci(KitSlice ext, KitSlice pat) { return 1; } +static KitStatus validate_frontend_vtable(const KitFrontendVTable* vtable) { + if (vtable) { + uint8_t mode = vtable->caps.lto_mode; + uint32_t i; + if (!vtable->new_frontend || !vtable->free_frontend || + mode > KIT_FRONTEND_LTO_OPAQUE) { + return KIT_INVALID; + } + if (mode == KIT_FRONTEND_LTO_CG) { + if (!vtable->compile_cg) return KIT_INVALID; + } else if (!vtable->compile_obj) { + return KIT_INVALID; + } + if (vtable->extension_kinds && vtable->nextensions && !vtable->extensions) + return KIT_INVALID; + for (i = 0; vtable->extension_kinds && i < vtable->nextensions; ++i) { + uint8_t k = vtable->extension_kinds[i]; + if (k != KIT_FRONTEND_PATH_SOURCE && k != KIT_FRONTEND_PATH_HEADER) + return KIT_INVALID; + } + } + return KIT_OK; +} + +static KitFrontendPathKind vtable_extension_kind( + const KitFrontendVTable* v, uint32_t index) { + if (!v || index >= v->nextensions) return KIT_FRONTEND_PATH_UNKNOWN; + if (!v->extension_kinds) return KIT_FRONTEND_PATH_SOURCE; + return (KitFrontendPathKind)v->extension_kinds[index]; +} + +static KitStatus registry_reserve(KitFrontendRegistry* r, uint32_t want) { + KitHeap* h; + const KitFrontendVTable** nv; + uint32_t ncap; + uint32_t i; + if (!r || !r->ctx || !r->ctx->heap) return KIT_INVALID; + if (want <= r->cap) return KIT_OK; + h = r->ctx->heap; + ncap = r->cap ? r->cap : 8u; + while (ncap < want) { + if (ncap > UINT32_MAX / 2u) { + ncap = want; + break; + } + ncap *= 2u; + } + if ((size_t)ncap > SIZE_MAX / sizeof(*r->frontends)) return KIT_NOMEM; + if (r->frontends) { + nv = (const KitFrontendVTable**)h->realloc( + h, (void*)r->frontends, r->cap * sizeof(*r->frontends), + ncap * sizeof(*r->frontends), _Alignof(const KitFrontendVTable*)); + } else { + nv = (const KitFrontendVTable**)h->alloc( + h, ncap * sizeof(*r->frontends), _Alignof(const KitFrontendVTable*)); + } + if (!nv) return KIT_NOMEM; + for (i = r->cap; i < ncap; ++i) nv[i] = NULL; + r->frontends = nv; + r->cap = ncap; + return KIT_OK; +} + +static KitStatus compiler_frontends_reserve(Compiler* c, uint32_t want) { + Heap* h; + const KitFrontendVTable** nv; + uint32_t ncap; + uint32_t i; + if (!c || !c->ctx || !c->ctx->heap) return KIT_INVALID; + if (want <= c->frontends_cap) return KIT_OK; + h = (Heap*)c->ctx->heap; + ncap = c->frontends_cap ? c->frontends_cap : 8u; + while (ncap < want) { + if (ncap > UINT32_MAX / 2u) { + ncap = want; + break; + } + ncap *= 2u; + } + if ((size_t)ncap > SIZE_MAX / sizeof(*c->frontends)) return KIT_NOMEM; + if (c->frontends) { + nv = (const KitFrontendVTable**)h->realloc( + h, (void*)c->frontends, c->frontends_cap * sizeof(*c->frontends), + ncap * sizeof(*c->frontends), _Alignof(const KitFrontendVTable*)); + } else { + nv = (const KitFrontendVTable**)h->alloc( + h, ncap * sizeof(*c->frontends), _Alignof(const KitFrontendVTable*)); + } + if (!nv) return KIT_NOMEM; + for (i = c->frontends_cap; i < ncap; ++i) nv[i] = NULL; + c->frontends = nv; + c->frontends_cap = ncap; + return KIT_OK; +} + +static KitStatus compiler_register_frontend(Compiler* c, KitLanguage lang, + const KitFrontendVTable* vtable) { + KitStatus st; + if (!c || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO) + return KIT_INVALID; + st = validate_frontend_vtable(vtable); + if (st != KIT_OK) return st; + st = compiler_frontends_reserve(c, lang + 1u); + if (st != KIT_OK) return st; + c->frontends[lang] = vtable; + if (c->nfrontends < lang + 1u) c->nfrontends = lang + 1u; + return KIT_OK; +} + /* 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]; + if (lang >= ((Compiler*)c)->nfrontends) return NULL; + return ((Compiler*)c)->frontends[lang]; } return lang_registry_vtable((KitLanguage)lang); } -KitLanguage kit_language_for_path(KitCompiler* c, const char* path) { +static const KitFrontendVTable* registry_frontend_for( + const KitFrontendRegistry* r, KitLanguage lang) { + if (!r || lang >= r->nfrontends) return NULL; + return r->frontends[lang]; +} + +static uint32_t frontend_count_for(KitCompiler* c) { + if (c) return ((Compiler*)c)->nfrontends; + return KIT_LANG_BUILTIN_COUNT; +} + +static KitFrontendPathKind language_path_kind_common( + KitCompiler* c, const KitFrontendRegistry* r, const char* path, + KitLanguage* out_lang) { size_t len; size_t i; KitSlice ext = SLICE_NULL; int have_ext = 0; - unsigned lang; + uint32_t lang; + uint32_t nlangs; - if (!path) return KIT_LANG_UNKNOWN; + if (out_lang) *out_lang = KIT_LANG_UNKNOWN; + if (!path) return KIT_FRONTEND_PATH_UNKNOWN; for (len = 0; path[len]; ++len) { } - /* Strip back to the last `.` after the final `/`. No dot → no - * extension → no language claims it. */ i = len; while (i > 0) { --i; @@ -117,19 +248,33 @@ KitLanguage kit_language_for_path(KitCompiler* c, const char* path) { break; } } - if (!have_ext) return KIT_LANG_UNKNOWN; + if (!have_ext) return KIT_FRONTEND_PATH_UNKNOWN; - for (lang = 0; lang < KIT_LANG_COUNT; ++lang) { - const KitFrontendVTable* v = frontend_for(c, lang); + nlangs = r ? r->nfrontends : frontend_count_for(c); + for (lang = 0; lang < nlangs; ++lang) { + const KitFrontendVTable* v = + r ? registry_frontend_for(r, lang) : frontend_for(c, lang); uint32_t e; if (!v || !v->extensions) continue; for (e = 0; e < v->nextensions; ++e) { - if (ext_eq_ci(ext, v->extensions[e])) return (KitLanguage)lang; + if (ext_eq_ci(ext, v->extensions[e])) { + if (out_lang) *out_lang = (KitLanguage)lang; + return vtable_extension_kind(v, e); + } } } - /* No registered frontend claims this extension. C is not special — it does - * not absorb unrecognized inputs. */ - return KIT_LANG_UNKNOWN; + return KIT_FRONTEND_PATH_UNKNOWN; +} + +KitLanguage kit_language_for_path(KitCompiler* c, const char* path) { + KitLanguage lang = KIT_LANG_UNKNOWN; + (void)language_path_kind_common(c, NULL, path, &lang); + return lang; +} + +KitFrontendPathKind kit_language_path_kind(KitCompiler* c, const char* path, + KitLanguage* out_lang) { + return language_path_kind_common(c, NULL, path, out_lang); } /* Case-sensitive trailing-substring match (mirrors the driver's exact suffix @@ -143,9 +288,10 @@ static int path_has_suffix(const char* s, const char* suffix) { KitInputKind kit_input_kind_for_path(KitCompiler* c, const char* path) { if (!path) return KIT_INPUT_UNKNOWN; - /* A registered frontend's extension wins first (case-insensitive registry, - * headers excluded — they claim no language). */ - if (kit_language_for_path(c, path) != KIT_LANG_UNKNOWN) + /* A registered frontend's compilable-source extension wins first + * (case-insensitive registry). Header/interface extensions are owned by a + * language but are not standalone translation units. */ + if (kit_language_path_kind(c, path, NULL) == KIT_FRONTEND_PATH_SOURCE) return KIT_INPUT_SOURCE; if (path_has_suffix(path, ".o") || path_has_suffix(path, ".obj")) return KIT_INPUT_OBJECT; @@ -168,9 +314,11 @@ static int name_eq(const char* name, KitSlice pat) { } KitLanguage kit_language_for_name(KitCompiler* c, const char* name) { - unsigned lang; + uint32_t lang; + uint32_t nlangs; if (!name) return KIT_LANG_UNKNOWN; - for (lang = 0; lang < KIT_LANG_COUNT; ++lang) { + nlangs = frontend_count_for(c); + for (lang = 0; lang < nlangs; ++lang) { const KitFrontendVTable* v = frontend_for(c, lang); uint32_t n; if (!v || !v->names) continue; @@ -183,7 +331,7 @@ KitLanguage kit_language_for_name(KitCompiler* c, const char* name) { const char* kit_language_name(KitCompiler* c, KitLanguage lang) { const KitFrontendVTable* v; - if ((unsigned)lang >= KIT_LANG_COUNT) return NULL; + if (lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO) 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 @@ -193,7 +341,7 @@ const char* kit_language_name(KitCompiler* c, KitLanguage lang) { const char* kit_language_default_extension(KitCompiler* c, KitLanguage lang) { const KitFrontendVTable* v; - if ((unsigned)lang >= KIT_LANG_COUNT) return NULL; + if (lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO) 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 @@ -204,29 +352,15 @@ const char* kit_language_default_extension(KitCompiler* c, KitLanguage lang) { KitStatus kit_register_frontend(KitCompiler* c, KitLanguage lang, const KitFrontendVTable* vtable) { - if (!c) return KIT_INVALID; - if ((unsigned)lang >= KIT_LANG_COUNT) return KIT_INVALID; - if (vtable) { - uint8_t mode = vtable->caps.lto_mode; - if (!vtable->new_frontend || !vtable->free_frontend || - mode > KIT_FRONTEND_LTO_OPAQUE) { - return KIT_INVALID; - } - if (mode == KIT_FRONTEND_LTO_CG) { - if (!vtable->compile_cg) return KIT_INVALID; - } else if (!vtable->compile_obj) { - return KIT_INVALID; - } - } - c->frontends[lang] = vtable; - return KIT_OK; + return compiler_register_frontend((Compiler*)c, lang, vtable); } KitStatus kit_frontend_caps(KitCompiler* c, KitLanguage lang, KitFrontendCaps* out) { const KitFrontendVTable* v; - if (!c || !out || (unsigned)lang >= KIT_LANG_COUNT) return KIT_INVALID; - v = c->frontends[lang]; + if (!c || !out || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO) + return KIT_INVALID; + v = frontend_for(c, lang); if (!v) return KIT_INVALID; *out = v->caps; return KIT_OK; @@ -236,19 +370,118 @@ KitStatus kit_frontend_parse_options(KitCompiler* c, KitLanguage lang, int argc, char** argv, void** out_opts) { const KitFrontendVTable* v; if (out_opts) *out_opts = NULL; - if (!c || !out_opts || (unsigned)lang >= KIT_LANG_COUNT) return KIT_INVALID; - v = c->frontends[lang]; + if (!c || !out_opts || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO) + return KIT_INVALID; + v = frontend_for(c, lang); if (!v || !v->parse_options) return KIT_INVALID; return v->parse_options(c, argc, argv, out_opts); } void kit_frontend_free_options(KitCompiler* c, KitLanguage lang, void* opts) { const KitFrontendVTable* v; - if (!c || !opts || (unsigned)lang >= KIT_LANG_COUNT) return; - v = c->frontends[lang]; + if (!c || !opts || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO) return; + v = frontend_for(c, lang); if (v && v->free_options) v->free_options(c, opts); } +KitStatus kit_frontend_registry_new(const KitContext* ctx, + KitFrontendRegistry** out) { + KitHeap* h; + KitFrontendRegistry* r; + if (!out) return KIT_INVALID; + *out = NULL; + if (!ctx || !ctx->heap) return KIT_INVALID; + h = ctx->heap; + r = (KitFrontendRegistry*)h->alloc(h, sizeof(*r), + _Alignof(KitFrontendRegistry)); + if (!r) return KIT_NOMEM; + memset(r, 0, sizeof(*r)); + r->ctx = ctx; + *out = r; + return KIT_OK; +} + +void kit_frontend_registry_free(KitFrontendRegistry* r) { + KitHeap* h; + if (!r || !r->ctx || !r->ctx->heap) return; + h = r->ctx->heap; + if (r->frontends) + h->free(h, (void*)r->frontends, r->cap * sizeof(*r->frontends)); + h->free(h, r, sizeof(*r)); +} + +KitStatus kit_frontend_registry_add_builtin(KitFrontendRegistry* r) { + KitStatus st; + KitLanguage lang; + if (!r) return KIT_INVALID; + st = registry_reserve(r, KIT_LANG_BUILTIN_COUNT); + if (st != KIT_OK) return st; + for (lang = 0; lang < KIT_LANG_BUILTIN_COUNT; ++lang) + r->frontends[lang] = lang_registry_vtable(lang); + if (r->nfrontends < KIT_LANG_BUILTIN_COUNT) + r->nfrontends = KIT_LANG_BUILTIN_COUNT; + return KIT_OK; +} + +KitStatus kit_frontend_registry_add(KitFrontendRegistry* r, + const KitFrontendVTable* vtable, + KitLanguage* out_lang) { + KitStatus st; + KitLanguage lang; + if (out_lang) *out_lang = KIT_LANG_UNKNOWN; + if (!r || !vtable) return KIT_INVALID; + st = validate_frontend_vtable(vtable); + if (st != KIT_OK) return st; + lang = r->nfrontends; + st = registry_reserve(r, lang + 1u); + if (st != KIT_OK) return st; + r->frontends[lang] = vtable; + r->nfrontends = lang + 1u; + if (out_lang) *out_lang = lang; + return KIT_OK; +} + +KitLanguage kit_frontend_registry_language_for_path( + const KitFrontendRegistry* r, const char* path) { + KitLanguage lang = KIT_LANG_UNKNOWN; + (void)language_path_kind_common(NULL, r, path, &lang); + return lang; +} + +KitLanguage kit_frontend_registry_language_for_name( + const KitFrontendRegistry* r, const char* name) { + uint32_t lang; + if (!r || !name) return KIT_LANG_UNKNOWN; + for (lang = 0; lang < r->nfrontends; ++lang) { + const KitFrontendVTable* v = registry_frontend_for(r, lang); + uint32_t n; + if (!v || !v->names) continue; + for (n = 0; n < v->nnames; ++n) { + if (name_eq(name, v->names[n])) return (KitLanguage)lang; + } + } + return KIT_LANG_UNKNOWN; +} + +KitFrontendPathKind kit_frontend_registry_path_kind( + const KitFrontendRegistry* r, const char* path, KitLanguage* out_lang) { + return language_path_kind_common(NULL, r, path, out_lang); +} + +KitStatus kit_compiler_install_frontend_registry( + KitCompiler* c, const KitFrontendRegistry* r) { + Compiler* cc = (Compiler*)c; + KitStatus st; + uint32_t i; + if (!cc || !r) return KIT_INVALID; + st = compiler_frontends_reserve(cc, r->nfrontends); + if (st != KIT_OK) return st; + for (i = 0; i < r->nfrontends; ++i) cc->frontends[i] = r->frontends[i]; + for (i = r->nfrontends; i < cc->nfrontends; ++i) cc->frontends[i] = NULL; + cc->nfrontends = r->nfrontends; + return KIT_OK; +} + uint32_t kit_compiler_arch_predefines(KitCompiler* c, const KitPredefinedMacro** out) { const ArchImpl* arch; @@ -270,7 +503,9 @@ static void validate_bytes(Compiler* c, const KitSourceInput* in) { static const KitFrontendVTable* frontend_for_language(Compiler* c, KitLanguage lang) { - if ((unsigned)lang >= KIT_LANG_COUNT) return NULL; + if (!c || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO || + lang >= c->nfrontends) + return NULL; return c->frontends[lang]; } @@ -289,7 +524,8 @@ static KitStatus kit_frontend_new(KitCompiler* c, KitLanguage lang, if (!out) return KIT_INVALID; *out = NULL; - if (!c || (unsigned)lang >= KIT_LANG_COUNT) return KIT_INVALID; + if (!c || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO) + return KIT_INVALID; vtable = frontend_for_language((Compiler*)c, lang); if (!vtable) return KIT_UNSUPPORTED; state = vtable->new_frontend(c); @@ -418,7 +654,9 @@ KitStatus kit_compile_session_new(KitCompiler* c, if (!out) return KIT_INVALID; *out = NULL; - if (!c || !opts || (unsigned)opts->lang >= KIT_LANG_COUNT) return KIT_INVALID; + if (!c || !opts || opts->lang == KIT_LANG_UNKNOWN || + opts->lang == KIT_LANG_AUTO) + return KIT_INVALID; st = kit_frontend_new(c, opts->lang, &frontend); if (st != KIT_OK) return st; h = (Heap*)c->ctx->heap; diff --git a/src/api/core.c b/src/api/core.c @@ -1,6 +1,7 @@ /* Public core API bridge. */ #include "core/core.h" +#include "api/lang_registry.h" #include <kit/core.h> #include <string.h> @@ -168,6 +169,12 @@ int kit_target_has_feature(const KitTarget* t, KitSlice name) { KitStatus kit_compiler_new(const KitTarget* target, const KitContext* ctx, KitCompiler** out) { + return kit_compiler_new_ex(target, ctx, NULL, out); +} + +KitStatus kit_compiler_new_ex(const KitTarget* target, const KitContext* ctx, + const KitCompilerOptions* opts, + KitCompiler** out) { Heap* h; Compiler* c; KitStatus st; @@ -182,6 +189,15 @@ KitStatus kit_compiler_new(const KitTarget* target, const KitContext* ctx, h->free(h, c, sizeof(*c)); return st; } + if (opts && opts->frontends) { + st = kit_compiler_install_frontend_registry(c, opts->frontends); + if (st != KIT_OK) { + compiler_fini(c); + h->free(h, c, sizeof(*c)); + if (out) *out = NULL; + return st; + } + } *out = c; return KIT_OK; } diff --git a/src/api/lang_registry.c b/src/api/lang_registry.c @@ -37,19 +37,36 @@ extern const KitFrontendVTable kit_asm_frontend_vtable; #include "wasm/wasm.h" #endif -void lang_registry_init(KitCompiler* c) { +KitStatus lang_registry_init(KitCompiler* c) { #if KIT_LANG_ASM_ENABLED - (void)kit_register_frontend(c, KIT_LANG_ASM, &kit_asm_frontend_vtable); + { + KitStatus st = + kit_register_frontend(c, KIT_LANG_ASM, &kit_asm_frontend_vtable); + if (st != KIT_OK) return st; + } #endif #if KIT_LANG_C_ENABLED - (void)kit_register_frontend(c, KIT_LANG_C, &kit_c_frontend_vtable); + { + KitStatus st = + kit_register_frontend(c, KIT_LANG_C, &kit_c_frontend_vtable); + if (st != KIT_OK) return st; + } #endif #if KIT_LANG_TOY_ENABLED - (void)kit_register_frontend(c, KIT_LANG_TOY, &kit_toy_frontend_vtable); + { + KitStatus st = + kit_register_frontend(c, KIT_LANG_TOY, &kit_toy_frontend_vtable); + if (st != KIT_OK) return st; + } #endif #if KIT_LANG_WASM_ENABLED - (void)kit_register_frontend(c, KIT_LANG_WASM, &kit_wasm_frontend_vtable); + { + KitStatus st = + kit_register_frontend(c, KIT_LANG_WASM, &kit_wasm_frontend_vtable); + if (st != KIT_OK) return st; + } #endif + return KIT_OK; } const KitFrontendVTable* lang_registry_vtable(KitLanguage lang) { diff --git a/src/api/lang_registry.h b/src/api/lang_registry.h @@ -6,7 +6,7 @@ /* 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); +KitStatus 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 @@ -14,5 +14,7 @@ void lang_registry_init(KitCompiler* c); * 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); +KitStatus kit_compiler_install_frontend_registry( + KitCompiler*, const KitFrontendRegistry*); #endif diff --git a/src/core/core.c b/src/core/core.c @@ -89,6 +89,7 @@ struct CompilerCleanup { KitStatus compiler_init(Compiler* c, const KitTarget* target, const KitContext* ctx) { Heap* h = ctx->heap; + KitStatus st; if (!c || !target || !ctx || !h) return KIT_INVALID; memset(c, 0, sizeof(*c)); @@ -116,12 +117,17 @@ KitStatus compiler_init(Compiler* c, const KitTarget* target, c->cleanup = NULL; - lang_registry_init(c); + st = lang_registry_init(c); + if (st != KIT_OK) goto err_status; return KIT_OK; nomem: compiler_fini(c); return KIT_NOMEM; + +err_status: + compiler_fini(c); + return st; } void compiler_fini(Compiler* c) { @@ -133,6 +139,12 @@ void compiler_fini(Compiler* c) { c->cg_api_free(c); c->cg_api_free = NULL; } + if (c->frontends) { + h->free(h, c->frontends, c->frontends_cap * sizeof(*c->frontends)); + c->frontends = NULL; + c->nfrontends = 0; + c->frontends_cap = 0; + } if (c->abi) { abi_free(c->abi); diff --git a/src/core/core.h b/src/core/core.h @@ -139,7 +139,9 @@ struct KitCompiler { const KitTarget* target_ref; Target target; CompilerCleanup* cleanup; - const KitFrontendVTable* frontends[KIT_LANG_COUNT]; + const KitFrontendVTable** frontends; + u32 nfrontends; + u32 frontends_cap; void* cg_api; void (*cg_api_free)(Compiler*); /* Borrowed pointer to the per-compiler builtin diff --git a/test/rpn-lang/caller.c b/test/rpn-lang/caller.c @@ -0,0 +1,5 @@ +int main(void); + +int _start(void) { + return main() == 14 ? 0 : 1; +} diff --git a/test/rpn-lang/prog.rpn b/test/rpn-lang/prog.rpn @@ -0,0 +1 @@ +2 3 4 * + diff --git a/test/rpn-lang/rpn_lang.c b/test/rpn-lang/rpn_lang.c @@ -0,0 +1,143 @@ +#include <kit/frontend.h> + +#include <stddef.h> +#include <stdint.h> + +typedef struct RpnFrontend { + KitCompiler* c; +} RpnFrontend; + +static KitFrontendState* rpn_new(KitCompiler* c) { + KitHeap* h = kit_compiler_context(c)->heap; + RpnFrontend* fe = (RpnFrontend*)h->alloc(h, sizeof(*fe), _Alignof(RpnFrontend)); + if (!fe) return NULL; + fe->c = c; + return (KitFrontendState*)fe; +} + +static void rpn_free(KitFrontendState* st) { + RpnFrontend* fe = (RpnFrontend*)st; + KitHeap* h; + if (!fe) return; + h = kit_compiler_context(fe->c)->heap; + h->free(h, fe, sizeof(*fe)); +} + +static void rpn_skip_ws(const char** p, const char* end) { + while (*p < end && (**p == ' ' || **p == '\t' || **p == '\r' || **p == '\n')) + ++(*p); +} + +static int rpn_read_int(const char** p, const char* end, int64_t* out) { + int neg = 0; + int any = 0; + int64_t v = 0; + if (*p < end && **p == '-') { + neg = 1; + ++(*p); + } + while (*p < end && **p >= '0' && **p <= '9') { + any = 1; + v = v * 10 + (int64_t)(**p - '0'); + ++(*p); + } + if (!any) return 0; + *out = neg ? -v : v; + return 1; +} + +static KitStatus rpn_compile_cg(KitFrontendState* st, + const KitFrontendCompileOptions* opts, + const KitSourceInput* input, KitCg* cg) { + RpnFrontend* fe = (RpnFrontend*)st; + const char* p = input->bytes.s; + const char* end = p + input->bytes.len; + KitCgTypeId i32_ty = kit_cg_type_builtin(fe->c, KIT_CG_BUILTIN_I32); + KitCgTypeId func_ty; + KitCgFuncResult result; + KitCgFuncSig sig; + KitCgDecl decl; + KitCgSym main_sym; + int depth = 0; + (void)opts; + + result.type = i32_ty; + result.attrs.flags = 0; + result.attrs.align = 0; + result.attrs.dereferenceable_size = 0; + sig.result = result; + sig.params = NULL; + sig.nparams = 0; + sig.call_conv = KIT_CG_CC_TARGET_C; + sig.abi_variadic = false; + func_ty = kit_cg_type_func(fe->c, sig); + + decl = (KitCgDecl){0}; + decl.kind = KIT_CG_DECL_FUNC; + decl.linkage_name = + kit_cg_c_linkage_name(fe->c, kit_sym_intern(fe->c, KIT_SLICE_LIT("main"))); + decl.display_name = kit_sym_intern(fe->c, KIT_SLICE_LIT("main")); + decl.type = func_ty; + decl.sym.bind = KIT_SB_GLOBAL; + decl.sym.visibility = KIT_CG_VIS_DEFAULT; + decl.sym.flags = 0; + decl.as.func.flags = 0; + decl.as.func.inline_policy = KIT_CG_INLINE_DEFAULT; + + main_sym = kit_cg_decl(cg, decl); + if (main_sym == KIT_CG_SYM_NONE) return KIT_ERR; + kit_cg_func_begin(cg, main_sym); + while (p < end) { + int64_t v; + rpn_skip_ws(&p, end); + if (p >= end) break; + if ((*p >= '0' && *p <= '9') || + (*p == '-' && p + 1 < end && p[1] >= '0' && p[1] <= '9')) { + if (!rpn_read_int(&p, end, &v)) return KIT_MALFORMED; + kit_cg_push_int(cg, (uint64_t)(int32_t)v, i32_ty); + ++depth; + continue; + } + if (*p == '+' || *p == '-' || *p == '*') { + char op = *p++; + if (depth < 2) return KIT_MALFORMED; + if (op == '+') + kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0); + else if (op == '-') + kit_cg_int_binop(cg, KIT_CG_INT_SUB, 0); + else + kit_cg_int_binop(cg, KIT_CG_INT_MUL, 0); + --depth; + continue; + } + return KIT_MALFORMED; + } + if (depth != 1) return KIT_MALFORMED; + kit_cg_ret(cg); + kit_cg_func_end(cg); + return KIT_OK; +} + +static const KitSlice rpn_exts[] = {KIT_SLICE_LIT("rpn")}; +static const KitSlice rpn_names[] = {KIT_SLICE_LIT("rpn")}; + +const KitFrontendVTable kit_rpn_frontend_vtable = { + .new_frontend = rpn_new, + .compile_cg = rpn_compile_cg, + .compile_obj = NULL, + .free_frontend = rpn_free, + .extensions = rpn_exts, + .nextensions = (uint32_t)(sizeof rpn_exts / sizeof rpn_exts[0]), + .extension_kinds = NULL, + .names = rpn_names, + .nnames = (uint32_t)(sizeof rpn_names / sizeof rpn_names[0]), + .commit = NULL, + .abort = NULL, + .caps = {false, KIT_FRONTEND_LTO_CG, false}, + .parse_options = NULL, + .free_options = NULL, +}; + +KitStatus kit_rpn_register_frontends(KitFrontendRegistry* registry) { + return kit_frontend_registry_add(registry, &kit_rpn_frontend_vtable, NULL); +} diff --git a/test/rpn-lang/run.sh b/test/rpn-lang/run.sh @@ -0,0 +1,46 @@ +#!/bin/sh +set -eu + +ROOT=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd) +KIT_BUILD_DIR=${KIT_BUILD_DIR:-"$ROOT/build/test/rpn-lang"} +OUT="$KIT_BUILD_DIR/out" +LOG="$KIT_BUILD_DIR/run.log" +RPN_OBJ="$OUT/rpn_lang.o" +KIT="$KIT_BUILD_DIR/kit-rpn" + +mkdir -p "$OUT" + +{ + printf 'build rpn frontend object\n' + "${CC:-cc}" -std=c11 -I "$ROOT/include" -c "$ROOT/test/rpn-lang/rpn_lang.c" -o "$RPN_OBJ" + printf 'build custom kit driver\n' + make -C "$ROOT" custom-bin \ + BUILD_DIR="$KIT_BUILD_DIR/base" \ + KIT_DRIVER_CUSTOM_BIN="$KIT" \ + KIT_DRIVER_EXT_OBJS="$RPN_OBJ" \ + KIT_DRIVER_EXT_REGISTER=kit_rpn_register_frontends + printf 'compile rpn program with custom driver\n' + "$KIT" build-exe -target x86_64-none-elf -nostdlib \ + "$ROOT/test/rpn-lang/caller.c" "$ROOT/test/rpn-lang/prog.rpn" \ + -o "$OUT/rpn.elf" + printf 'inspect symbols\n' + "$KIT" nm "$OUT/rpn.elf" + printf 'run rpn program with custom driver\n' + if "$KIT" run "$ROOT/test/rpn-lang/prog.rpn"; then + rpn_status=0 + else + rpn_status=$? + fi + if [ "$rpn_status" -ne 14 ]; then + printf 'expected kit run exit 14, got %s\n' "$rpn_status" + exit 1 + fi + printf 'debugger accepts rpn input with custom driver\n' + "$KIT" dbg "$ROOT/test/rpn-lang/prog.rpn" --batch --command language + printf 'debugger appends rpn snippets with custom driver\n' + "$KIT" dbg --batch --language rpn --command 'jit { 2 3 + }' --command language +} >"$LOG" 2>&1 + +grep ' main$' "$LOG" >/dev/null +grep 'Language: rpn' "$LOG" >/dev/null +printf 'rpn-lang ok\n'