kit

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

commit df8ad4cefccdc4ceab566808818f8ec5894e3545
parent b47f65e8fc00c584505e9490181138891e1a9059
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  2 Jun 2026 20:11:07 -0700

driver: add generic `compile` front door; frontends own their flags

Split the CLI-dialect axis: `cc` stays the GCC-compatible C driver+linker
(unchanged, still accepts non-C by suffix); a new `compile` tool is the
kit-native compiler that resolves exactly one frontend per invocation (by
-x or suffix), emits objects / -S / --emit=c / --emit=ir or checks, and does
not link. Both shells drive one language-neutral engine.

- Hoist KitPreprocessOptions out of KitCCompileOptions into the common
  KitFrontendCompileOptions so the generic driver needs no per-language
  special-casing; the C frontend reads code/preprocess from the common
  options and no longer uses language_options.
- KitFrontendVTable gains a KitFrontendCaps capability descriptor plus
  parse_options/free_options hooks, surfaced via kit_frontend_caps /
  kit_frontend_parse_options / kit_frontend_free_options. The active
  frontend decides whether the preprocessor flags apply, and any flag the
  driver does not consume is handed to the frontend.
- First real consumer: public KitWasmCompileOptions + a wasm option parser
  for -mfeature= / -mno-feature= (default: full feature set, as before).
- Extract driver/lib/compile_engine.c (driver_compile_run), shared by cc
  and compile; cc refactored onto it (behavior-preserving).
- New driver/cmd/compile.c wired through config.h / driver.h / main.c /
  Makefile; test/compile/run.sh (test-driver-compile, 27 checks).
- Refresh the stale toy-local-inspection dbg golden (helper+0x78 -> 0x74),
  a pre-existing -O0 codegen offset drift unrelated to this change.

Tests: test-pp/parse/cg-api/toy/isa/smoke-x64/dbg + full test-driver green.

Diffstat:
MMakefile | 6++++++
Mdoc/DRIVER.md | 22+++++++++++++++++++++-
Mdriver/cmd/cc.c | 77+++++++++++++++++++++++------------------------------------------------------
Adriver/cmd/compile.c | 562+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/cmd/dbg.c | 17++++++++++-------
Mdriver/cmd/run.c | 5+++--
Mdriver/driver.h | 2++
Adriver/lib/compile_engine.c | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adriver/lib/compile_engine.h | 35+++++++++++++++++++++++++++++++++++
Mdriver/lib/inputs.c | 12+++---------
Mdriver/lib/inputs.h | 13++++++++-----
Mdriver/lib/runtime.c | 9+++++----
Mdriver/main.c | 5+++++
Minclude/kit/compile.h | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Minclude/kit/config.h | 1+
Mlang/c/c.c | 22+++++++++++-----------
Mlang/toy/compile.c | 3+++
Mlang/wasm/wasm.c | 109++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/api/compile.c | 34++++++++++++++++++++++++++++++++--
Atest/compile/run.sh | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/dbg/cases/toy-local-inspection/expected | 2+-
Mtest/parse/harness/parse_runner.c | 32+++++++++++++++++++-------------
Mtest/test.mk | 6+++++-
23 files changed, 1086 insertions(+), 114 deletions(-)

diff --git a/Makefile b/Makefile @@ -352,6 +352,9 @@ endif ifeq ($(KIT_TOOL_CHECK_ENABLED),1) DRIVER_TOOL_SRCS += driver/cmd/cc.c endif +ifeq ($(KIT_TOOL_COMPILE_ENABLED),1) +DRIVER_TOOL_SRCS += driver/cmd/compile.c +endif ifeq ($(KIT_TOOL_INSTALL_ENABLED),1) DRIVER_TOOL_SRCS += driver/cmd/install.c endif @@ -446,6 +449,9 @@ endif ifneq ($(filter 1,$(KIT_TOOL_AR_ENABLED) $(KIT_TOOL_RANLIB_ENABLED) $(KIT_TOOL_STRIP_ENABLED) $(KIT_TOOL_DBG_ENABLED) $(KIT_TOOL_RUN_ENABLED)),) DRIVER_SRCS += driver/lib/inputs.c endif +ifneq ($(filter 1,$(KIT_TOOL_CC_ENABLED) $(KIT_TOOL_CHECK_ENABLED) $(KIT_TOOL_COMPILE_ENABLED)),) +DRIVER_SRCS += driver/lib/compile_engine.c +endif DRIVER_SRCS := $(sort $(DRIVER_SRCS)) DRIVER_OBJS = $(patsubst driver/%.c,$(BUILD_DIR)/driver/%.o,$(DRIVER_SRCS)) DRIVER_DEPS = $(DRIVER_OBJS:.o=.d) diff --git a/doc/DRIVER.md b/doc/DRIVER.md @@ -1,7 +1,7 @@ # DRIVER The `kit` multitool is the toolchain's only executable: a single binary that -dispatches to ~26 named tools (compiler, assembler, linker, archive/object +dispatches to ~27 named tools (compiler, assembler, linker, archive/object utilities, byte utilities, JIT runner, debugger, emulator, packager, and an `install` command that lays down the per-tool links). It is also the first and canonical *consumer* of libkit — it depends only on the public API under @@ -85,6 +85,7 @@ tool reaches into compiler internals. |------|------| | `cc` | C compiler driver: compile, optionally link; preprocess (`-E`), dep-emit (`-M*`), `-shared`. GCC flag subset. Resolves `-l`/`-L` to concrete archive paths. | | `check` | Run the C frontend checks with no code emission. | +| `compile` | Kit-native single-language source compiler. Resolves one frontend (by `-x` or suffix) and emits objects / `-S` / `--emit=c` / `--emit=ir`, or checks (`-fsyntax-only`). Does **not** link (`.o`/`.a` are refused). The resolved frontend gates the preprocessor flags and parses its own extras (e.g. wasm `-mfeature=`). | | `install` | Lay down per-tool links (symlinks; hard links on Windows) in a target dir so the toolchain works under bare names (`cc`, `ld`, `nm`, …). Default set is the toolchain + standard-named byte utils; `--all` / explicit names override. | | `cpp` | Standalone preprocessor (alias for `cc -E` without link scaffolding). | | `as` | Assemble one GAS-subset text source to a relocatable object. | @@ -111,6 +112,17 @@ tool reaches into compiler internals. argv. `cc` and `run` overlap heavily on input shape and the preprocessor flag family — that overlap is exactly what `driver/lib/` factors out. +`cc` vs `compile`: `cc` is the GCC-compatible C driver — a drop-in `cc`/`clang` +for build systems, with the full Unix-toolchain flag surface (`-Wl,`, `-M*`, +sysroots, hosted-libc expansion, `-l`/`-L`) and a linker. It still accepts +non-C sources by suffix. `compile` is the kit-native front door: one frontend +per run (chosen by `-x` or suffix), no link, and a clean surface where each +frontend *owns* its flags — the frontend's `KitFrontendCaps` decides whether +the preprocessor family applies, and anything `compile` does not consume is +handed to the frontend's `parse_options` hook (e.g. wasm `-mfeature=`). Both +shells drive the same language-neutral compile step in +`driver/lib/compile_engine.c`. + `run` doubles as a `#!` script interpreter so a C file can be made executable and run directly. The kernel's shebang mechanism appends the script path *and* the user's arguments after the interpreter's flags, with no way to inject a @@ -139,6 +151,14 @@ and consistent. All are freestanding (no host calls except through `env/`). policy (`driver_default_pic`, `driver_link_pie`). Hosted targets default to PIE; freestanding and WASM stay non-PIE. Lives outside `env/` precisely because it touches no host state. +- **compile_engine** (`lib/compile_engine.c`): the language-neutral "compile + one source" step shared by `cc` and `compile`. Given a resolved + `KitLanguage`, `KitCodeOptions`/`KitDiagnosticOptions`, optional preprocessor + settings, and an opaque per-frontend `language_options` blob, it drives a + `KitCompileSession` and either returns the object builder (link/check) or + emits to a writer routed by the `emit_*` mode (object bytes, `.s`, or the + in-CG C-source / IR dump). It holds no policy — both tool shells supply the + options. - **inputs** (`lib/inputs.c`): classifies a mixed positional list (`-` stdin source, `.c`/`.s`/`.wat`/… sources, `.o` objects, `.a` archives) into parallel arrays, then loads + compiles + JIT-links them for `run`/`dbg`. Also diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c @@ -9,6 +9,7 @@ #include <string.h> #include "cflags.h" +#include "compile_engine.h" #include "driver.h" #include "hosted.h" #include "lib_resolve.h" @@ -2262,8 +2263,7 @@ out: return rc; } -static void cc_fill_c_opts(const CcOptions* o, const KitPreprocessOptions* pp, - KitCCompileOptions* copts) { +static void cc_fill_c_opts(const CcOptions* o, KitCCompileOptions* copts) { KitCCompileOptions zero = {0}; *copts = zero; copts->code.opt_level = o->syntax_only ? 0 : o->opt_level; @@ -2278,7 +2278,6 @@ static void cc_fill_c_opts(const CcOptions* o, const KitPreprocessOptions* pp, copts->code.epoch = o->epoch; copts->code.path_map = o->npath_map ? o->path_map : NULL; copts->code.npath_map = o->npath_map; - copts->preprocess = *pp; copts->diagnostics.warnings_are_errors = o->warnings_are_errors; copts->diagnostics.max_errors = o->max_errors; } @@ -2320,57 +2319,25 @@ out: return rc; } +/* Compile one source to an object builder via the shared engine. cc never + * passes frontend-specific language_options (it has no flag surface for them); + * those are the `compile` tool's domain. */ static KitStatus cc_compile_source_obj(KitCompiler* compiler, KitLanguage lang, const KitCCompileOptions* copts, + const KitPreprocessOptions* pp, KitSlice name, const KitSlice* input, KitObjBuilder** out) { - KitCompileSessionOptions sopts; - KitCompileSession* session = NULL; - KitSourceInput sin; - KitAsmCompileOptions aopts; - KitStatus st; - - memset(&sopts, 0, sizeof(sopts)); - memset(&aopts, 0, sizeof(aopts)); - sopts.lang = lang; - sopts.compile.code = copts->code; - sopts.compile.diagnostics = copts->diagnostics; - if (lang == KIT_LANG_ASM) { - aopts.code = copts->code; - aopts.diagnostics = copts->diagnostics; - sopts.compile.language_options = &aopts; - } else { - sopts.compile.language_options = copts; - } - memset(&sin, 0, sizeof(sin)); - sin.name = name; - sin.bytes = *input; - sin.lang = lang; - st = kit_compile_session_new(compiler, &sopts, &session); - if (st == KIT_OK) st = kit_compile_session_compile(session, &sin, out); - kit_compile_session_free(session); - return st; + return driver_compile_run(compiler, lang, &copts->code, &copts->diagnostics, + pp, NULL, name, input, NULL, out); } static KitStatus cc_compile_source_emit(KitCompiler* compiler, KitLanguage lang, const KitCCompileOptions* copts, + const KitPreprocessOptions* pp, KitSlice name, const KitSlice* input, KitWriter* out) { - KitObjBuilder* ob = NULL; - KitStatus st = cc_compile_source_obj(compiler, lang, copts, name, input, &ob); - if (st == KIT_OK) { - if (copts->code.emit_c_source) { - /* c_source_writer is wired during CG; nothing to do here. */ - } else if (copts->code.emit_ir) { - /* ir_dump_writer is wired during CG; nothing to do here. */ - } else if (copts->code.emit_asm_source) { - st = kit_obj_builder_emit_asm(ob, out); - } else { - st = kit_obj_builder_emit(ob, out); - } - } - kit_obj_builder_free(ob); - return st; + return driver_compile_run(compiler, lang, &copts->code, &copts->diagnostics, + pp, NULL, name, input, out, NULL); } static int cc_run_compile_one(DriverEnv* env, const CcOptions* o, @@ -2410,7 +2377,7 @@ static int cc_run_compile_one(DriverEnv* env, const CcOptions* o, goto out; } - cc_fill_c_opts(o, pp, &copts); + cc_fill_c_opts(o, &copts); if (copts.code.emit_c_source) { /* --emit=c routes the output writer to the C-source CGTarget instead of * the object emitter. The downstream `kit_compile_*_emit` path will @@ -2431,7 +2398,8 @@ static int cc_run_compile_one(DriverEnv* env, const CcOptions* o, KitSlice in_name = is_memory ? o->source_memory[index].name : kit_slice_cstr(o->source_files[index]); KitStatus st; - st = cc_compile_source_emit(compiler, lang, &copts, in_name, &input, obj_w); + st = cc_compile_source_emit(compiler, lang, &copts, pp, in_name, &input, + obj_w); if (st != KIT_OK) goto out; } @@ -2514,12 +2482,12 @@ static int cc_run_check(DriverEnv* env, const CcOptions* o, goto out; } - cc_fill_c_opts(o, pp, &copts); + cc_fill_c_opts(o, &copts); for (i = 0; i < o->nsource_files; ++i) { KitObjBuilder* ob = NULL; KitLanguage lang = cc_resolve_lang(compiler, o->source_files[i], o->source_langs[i]); - KitStatus st = cc_compile_source_obj(compiler, lang, &copts, + KitStatus st = cc_compile_source_obj(compiler, lang, &copts, pp, kit_slice_cstr(o->source_files[i]), &src_bytes[i], &ob); kit_obj_builder_free(ob); @@ -2528,7 +2496,7 @@ static int cc_run_check(DriverEnv* env, const CcOptions* o, for (i = 0; i < o->nsource_memory; ++i) { KitObjBuilder* ob = NULL; KitStatus st = cc_compile_source_obj(compiler, o->source_memory[i].lang, - &copts, o->source_memory[i].name, + &copts, pp, o->source_memory[i].name, &o->source_memory[i].bytes, &ob); kit_obj_builder_free(ob); if (st != KIT_OK) goto out; @@ -2670,21 +2638,22 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, if (kit_link_script_parse(&ctx, script_text, &script) != KIT_OK) goto out; } - cc_fill_c_opts(o, pp, &copts); + cc_fill_c_opts(o, &copts); for (i = 0; i < o->nsource_files; ++i) { KitLanguage lang = cc_resolve_lang(compiler, o->source_files[i], o->source_langs[i]); KitStatus st; - st = cc_compile_source_obj(compiler, lang, &copts, + st = cc_compile_source_obj(compiler, lang, &copts, pp, kit_slice_cstr(o->source_files[i]), &src_bytes[i], &objs[i]); if (st != KIT_OK) goto out; } for (i = 0; i < o->nsource_memory; ++i) { KitStatus st; - st = cc_compile_source_obj( - compiler, o->source_memory[i].lang, &copts, o->source_memory[i].name, - &o->source_memory[i].bytes, &objs[o->nsource_files + i]); + st = cc_compile_source_obj(compiler, o->source_memory[i].lang, &copts, pp, + o->source_memory[i].name, + &o->source_memory[i].bytes, + &objs[o->nsource_files + i]); if (st != KIT_OK) goto out; } diff --git a/driver/cmd/compile.c b/driver/cmd/compile.c @@ -0,0 +1,562 @@ +#include <kit/compile.h> +#include <kit/core.h> +#include <kit/preprocess.h> +#include <stdint.h> +#include <string.h> + +#include "cflags.h" +#include "compile_engine.h" +#include "driver.h" +#include "runtime.h" + +/* `kit compile` — kit-native, single-language source compiler. + * + * Unlike `cc` (the GCC-compatible C driver + linker), `compile` resolves + * exactly one frontend per invocation — from `-x` or the source suffix — and + * does NOT link: it produces objects (or `-S`/`--emit=c`/`--emit=ir`) and can + * check. The active frontend dictates which extra flags are accepted: it + * declares whether the preprocessor family (-I/-isystem/-D/-U) applies, and + * any flag `compile` does not own itself is handed to the frontend's own + * option parser (e.g. wasm `-mfeature=...`). `.o`/`.a` inputs are rejected — + * use `cc`, `ld`, or `run` to link. */ + +#define COMPILE_TOOL "compile" + +typedef enum CompileEmit { + COMPILE_EMIT_OBJ = 0, + COMPILE_EMIT_ASM, + COMPILE_EMIT_C, + COMPILE_EMIT_IR, +} CompileEmit; + +typedef struct CompileOptions { + DriverEnv* env; + const char* driver_path; + DriverCflags cf; + int cpp_flags_used; + + const char** sources; /* source paths (argv-borrowed) */ + uint32_t nsources; + + char** fe_args; /* leftover frontend flags (argv-borrowed) */ + uint32_t nfe_args; + + int forced_lang; /* -1 = resolve by suffix; else KitLanguage */ + int opt_level; + int debug_info; + int check_only; + int emit; /* CompileEmit */ + const char* output_path; + int warnings_are_errors; + uint32_t max_errors; + KitTarget target; +} CompileOptions; + +static void compile_usage(void) { + driver_errf(COMPILE_TOOL, "%.*s", + KIT_SLICE_ARG(KIT_SLICE_LIT( + "usage: kit compile [options] source...\n" + " kit compile --help for full option reference"))); +} + +void driver_help_compile(void) { + driver_printf( + "%.*s", + KIT_SLICE_ARG(KIT_SLICE_LIT( + "kit compile — compile one frontend's sources (no link)\n" + "\n" + "USAGE\n" + " kit compile [options] source...\n" + "\n" + "DESCRIPTION\n" + " Compiles each source to an object (default) or to assembly / C /\n" + " semantic IR. Every source must belong to ONE language, chosen by\n" + " -x or by file suffix (.c .s .toy .wat .wasm). `compile` does not\n" + " link: pass .o/.a to `cc`, `ld`, or `run` instead.\n" + "\n" + " The resolved frontend decides which extra flags apply. Only a\n" + " preprocessor-enabled frontend (C) accepts -I/-isystem/-D/-U; any\n" + " other unrecognized flag is handed to the frontend's own parser\n" + " (e.g. wasm: -mfeature=NAME / -mno-feature=NAME).\n" + "\n" + "OPTIONS\n" + " -c Compile to object (the default; accepted for " + "parity)\n" + " -S Emit assembly (.s)\n" + " --emit=obj|asm|c|ir Select output form (ir requires -O1+)\n" + " -fsyntax-only Check only; write no output\n" + " -o PATH Output path (required with multiple sources or\n" + " --emit=c; otherwise a default name is derived)\n" + " -O0 -O1 -O2 Optimization level (default -O0)\n" + " -g Emit DWARF debug info\n" + " -x LANG Force language: c | asm | toy | wasm\n" + " -I/-isystem/-D/-U Preprocessor flags (preprocessor frontends " + "only)\n" + " -target TRIPLE Cross-compile target (see `kit cc --help`)\n" + " -Werror Treat warnings as errors\n" + " -fmax-errors=N Stop after N errors (0 = unlimited)\n" + " -h, --help Show this help and exit\n" + "\n" + "EXIT CODES\n" + " 0 success 1 compile error 2 bad " + "usage\n"))); +} + +static int compile_parse_u64(const char* s, uint64_t* out) { + uint64_t v = 0; + int any = 0; + if (!s) return 1; + while (*s) { + unsigned d; + if (*s < '0' || *s > '9') return 1; + d = (unsigned)(*s - '0'); + if (v > (UINT64_MAX - d) / 10u) return 1; + v = v * 10u + d; + any = 1; + s++; + } + if (!any) return 1; + *out = v; + return 0; +} + +static int compile_set_lang(CompileOptions* o, const char* name) { + if (driver_streq(name, "c")) { + o->forced_lang = KIT_LANG_C; + } else if (driver_streq(name, "asm") || driver_streq(name, "s")) { + o->forced_lang = KIT_LANG_ASM; + } else if (driver_streq(name, "toy")) { + o->forced_lang = KIT_LANG_TOY; + } else if (driver_streq(name, "wasm") || driver_streq(name, "wat")) { + o->forced_lang = KIT_LANG_WASM; + } else { + driver_errf(COMPILE_TOOL, "unsupported -x language: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(name))); + return 1; + } + return 0; +} + +/* Reject obvious link inputs with a clear pointer to the right tool. */ +static int compile_is_link_input(const char* a) { + return driver_has_suffix(a, ".o") || driver_has_suffix(a, ".obj") || + driver_has_suffix(a, ".a") || driver_has_suffix(a, ".so") || + driver_has_suffix(a, ".dylib") || driver_has_suffix(a, ".tbd"); +} + +static int compile_classify_positional(CompileOptions* o, const char* a) { + if (driver_streq(a, "-")) { + driver_errf(COMPILE_TOOL, + "stdin ('-') is not supported; pass a source file"); + return 1; + } + if (compile_is_link_input(a)) { + driver_errf(COMPILE_TOOL, + "compile does not link; pass %.*s to `cc`, `ld`, or `run`", + KIT_SLICE_ARG(kit_slice_cstr(a))); + return 1; + } + o->sources[o->nsources++] = a; + return 0; +} + +static int compile_parse(int argc, char** argv, CompileOptions* o) { + int i; + o->forced_lang = -1; + o->target = driver_host_target(); + + for (i = 1; i < argc; ++i) { + const char* a = argv[i]; + int r; + + r = driver_cflags_try_consume(&o->cf, o->env, COMPILE_TOOL, argc, argv, &i); + if (r < 0) return 1; + if (r > 0) { + o->cpp_flags_used = 1; + continue; + } + + if (driver_streq(a, "-c")) { + o->emit = COMPILE_EMIT_OBJ; + continue; + } + if (driver_streq(a, "-S")) { + o->emit = COMPILE_EMIT_ASM; + continue; + } + if (driver_streq(a, "--emit=obj")) { + o->emit = COMPILE_EMIT_OBJ; + continue; + } + if (driver_streq(a, "--emit=asm")) { + o->emit = COMPILE_EMIT_ASM; + continue; + } + if (driver_streq(a, "--emit=c")) { + o->emit = COMPILE_EMIT_C; + continue; + } + if (driver_streq(a, "--emit=ir")) { + o->emit = COMPILE_EMIT_IR; + continue; + } + if (driver_streq(a, "-fsyntax-only") || driver_streq(a, "--check")) { + o->check_only = 1; + continue; + } + if (driver_streq(a, "-g")) { + o->debug_info = 1; + continue; + } + if (driver_streq(a, "-O0")) { + o->opt_level = 0; + continue; + } + if (driver_streq(a, "-O1")) { + o->opt_level = 1; + continue; + } + if (driver_streq(a, "-O2")) { + o->opt_level = 2; + continue; + } + if (driver_streq(a, "-Werror")) { + o->warnings_are_errors = 1; + continue; + } + if (driver_strneq(a, "-fmax-errors=", 13)) { + uint64_t v; + if (compile_parse_u64(a + 13, &v) != 0 || v > 0xFFFFFFFFu) { + driver_errf(COMPILE_TOOL, + "-fmax-errors= requires a non-negative integer"); + return 1; + } + o->max_errors = (uint32_t)v; + continue; + } + if (driver_streq(a, "-o")) { + if (++i >= argc) { + driver_errf(COMPILE_TOOL, "-o requires an argument"); + return 1; + } + o->output_path = argv[i]; + continue; + } + if (driver_strneq(a, "-o", 2)) { + o->output_path = a + 2; + continue; + } + if (driver_streq(a, "-x")) { + if (++i >= argc) { + driver_errf(COMPILE_TOOL, "-x requires an argument"); + return 1; + } + if (compile_set_lang(o, argv[i]) != 0) return 1; + continue; + } + if (driver_streq(a, "-target")) { + if (++i >= argc) { + driver_errf(COMPILE_TOOL, "-target requires an argument"); + return 1; + } + if (driver_target_from_triple(argv[i], &o->target) != 0) { + driver_errf(COMPILE_TOOL, "unrecognized target: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(argv[i]))); + return 1; + } + continue; + } + if (driver_strneq(a, "--target=", 9)) { + if (driver_target_from_triple(a + 9, &o->target) != 0) { + driver_errf(COMPILE_TOOL, "unrecognized target: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(a + 9))); + return 1; + } + continue; + } + + if (driver_streq(a, "--")) { + for (++i; i < argc; ++i) + if (compile_classify_positional(o, argv[i]) != 0) return 1; + break; + } + + /* A leftover flag belongs to the active frontend; defer it. A bare + * positional is a source. */ + if (a[0] == '-' && a[1] != '\0') { + o->fe_args[o->nfe_args++] = (char*)a; + continue; + } + if (compile_classify_positional(o, a) != 0) return 1; + } + + if (o->nsources == 0) { + driver_errf(COMPILE_TOOL, "no input files"); + compile_usage(); + return 1; + } + if (o->output_path && o->nsources > 1) { + driver_errf(COMPILE_TOOL, "-o cannot be used with multiple sources"); + return 1; + } + if (o->emit == COMPILE_EMIT_C && !o->output_path && !o->check_only) { + driver_errf(COMPILE_TOOL, "--emit=c requires -o"); + return 1; + } + if (o->emit == COMPILE_EMIT_IR && o->opt_level < 1) { + driver_errf(COMPILE_TOOL, + "--emit=ir requires -O1 or higher (the IR tape is only " + "recorded when the optimizer runs)"); + return 1; + } + return 0; +} + +/* Derive `<base>.<ext>` next to nothing (basename only), where ext follows the + * emit mode and target. Caller frees via driver_free(env, p, *out_size). */ +static char* compile_default_out(DriverEnv* env, const CompileOptions* o, + const char* src, size_t* out_size) { + int win = (o->target.os == KIT_OS_WINDOWS); + const char* ext; + size_t ext_len; + size_t srclen = driver_strlen(src); + size_t dot = srclen; + size_t slash = 0; + size_t k; + switch ((CompileEmit)o->emit) { + case COMPILE_EMIT_ASM: + ext = ".s"; + ext_len = 2u; + break; + case COMPILE_EMIT_IR: + ext = ".ir"; + ext_len = 3u; + break; + case COMPILE_EMIT_C: + ext = ".c"; + ext_len = 2u; + break; + default: + ext = win ? ".obj" : ".o"; + ext_len = win ? 4u : 2u; + break; + } + for (k = srclen; k > 0; --k) { + if (src[k - 1] == '.') { + dot = k - 1; + break; + } + if (src[k - 1] == '/') break; + } + for (k = dot; k > 0; --k) { + if (src[k - 1] == '/') { + slash = k; + break; + } + } + { + size_t name_len = dot - slash; + size_t bufsz = name_len + ext_len + 1u; + char* buf = driver_alloc(env, bufsz); + if (!buf) return NULL; + driver_memcpy(buf, src + slash, name_len); + driver_memcpy(buf + name_len, ext, ext_len); + buf[name_len + ext_len] = '\0'; + *out_size = bufsz; + return buf; + } +} + +/* Compile one source. out_path is NULL for check-only. */ +static int compile_one(const KitContext* ctx, KitCompiler* compiler, + KitLanguage lang, + const KitCodeOptions* code, + const KitDiagnosticOptions* diag, + const KitPreprocessOptions* pp, const void* lang_extra, + const char* src, const char* out_path) { + KitFileData fd = {0}; + KitSlice input; + KitWriter* w = NULL; + KitObjBuilder* ob = NULL; + KitStatus st; + int rc = 1; + + if (ctx->file_io->read_all(ctx->file_io->user, src, &fd) != KIT_OK) { + driver_errf(COMPILE_TOOL, "failed to read: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(src))); + return 1; + } + input.data = fd.data; + input.len = fd.size; + + if (out_path) { + if (ctx->file_io->open_writer(ctx->file_io->user, out_path, &w) != KIT_OK) { + driver_errf(COMPILE_TOOL, "failed to open output: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(out_path))); + goto out; + } + st = driver_compile_run(compiler, lang, code, diag, pp, lang_extra, + kit_slice_cstr(src), &input, w, NULL); + } else { + /* check-only: build and drop the object. */ + st = driver_compile_run(compiler, lang, code, diag, pp, lang_extra, + kit_slice_cstr(src), &input, NULL, &ob); + kit_obj_builder_free(ob); + } + rc = (st == KIT_OK) ? 0 : 1; + +out: + if (w) kit_writer_close(w); + ctx->file_io->release(ctx->file_io->user, &fd); + return rc; +} + +int driver_compile(int argc, char** argv) { + DriverEnv env; + CompileOptions o = {0}; + DriverRuntimeSupport runtime = {0}; + int runtime_resolved = 0; + KitContext ctx; + KitCompiler* compiler = NULL; + KitFrontendCaps caps = {0}; + KitLanguage lang = KIT_LANG_UNKNOWN; + KitCodeOptions code = {0}; + KitDiagnosticOptions diag = {0}; + KitPreprocessOptions pp; + void* lang_extra = NULL; + uint32_t i; + int rc = 2; + + if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { + driver_help_compile(); + return 0; + } + + driver_env_init(&env); + o.env = &env; + o.driver_path = argv[0]; + + o.sources = driver_alloc_zeroed(&env, (size_t)argc * sizeof(*o.sources)); + o.fe_args = driver_alloc_zeroed(&env, (size_t)argc * sizeof(*o.fe_args)); + if (!o.sources || !o.fe_args || driver_cflags_init(&o.cf, &env, argc) != 0) { + driver_errf(COMPILE_TOOL, "out of memory"); + goto done_early; + } + + if (compile_parse(argc, argv, &o) != 0) goto done; + + /* Freestanding runtime headers (mirrors `cc`): required once we have + * sources, so C/asm `#include`s resolve; harmless for toy/wasm. */ + if (driver_runtime_resolve(&env, NULL, o.driver_path, &runtime) == 0) { + runtime_resolved = 1; + if (driver_runtime_add_freestanding_headers(&runtime, &o.cf) != 0) { + driver_errf(COMPILE_TOOL, "failed to add freestanding headers"); + rc = 1; + goto done; + } + } else { + driver_errf(COMPILE_TOOL, "support dir not found"); + rc = 1; + goto done; + } + + ctx = driver_env_to_context(&env); + if (driver_compiler_new(o.target, &ctx, &compiler) != KIT_OK) { + driver_errf(COMPILE_TOOL, "failed to initialize compiler"); + rc = 1; + goto done; + } + + /* Resolve the single language: -x wins, else the first source's suffix. + * Require every source to agree. */ + if (o.forced_lang >= 0) { + lang = (KitLanguage)o.forced_lang; + } else { + lang = kit_language_for_path(compiler, o.sources[0]); + if (lang == KIT_LANG_UNKNOWN) { + driver_errf(COMPILE_TOOL, + "cannot determine language for %.*s (use -x LANG)", + KIT_SLICE_ARG(kit_slice_cstr(o.sources[0]))); + rc = 1; + goto done; + } + for (i = 1; i < o.nsources; ++i) { + if (kit_language_for_path(compiler, o.sources[i]) != lang) { + driver_errf(COMPILE_TOOL, + "all sources must be one language; %.*s differs (use -x)", + KIT_SLICE_ARG(kit_slice_cstr(o.sources[i]))); + rc = 1; + goto done; + } + } + } + + if (kit_frontend_caps(compiler, lang, &caps) != KIT_OK) { + driver_errf(COMPILE_TOOL, "no frontend registered for the input language"); + rc = 1; + goto done; + } + if (o.cpp_flags_used && !caps.preprocessor) { + driver_errf(COMPILE_TOOL, + "this frontend does not accept preprocessor flags " + "(-I/-isystem/-D/-U)"); + rc = 1; + goto done; + } + if (o.nfe_args) { + if (kit_frontend_parse_options(compiler, lang, (int)o.nfe_args, o.fe_args, + &lang_extra) != KIT_OK) { + /* The frontend emitted a specific diagnostic when it has a parser; + * add a generic line for frontends that accept no extra flags. */ + driver_errf(COMPILE_TOOL, "unsupported option for this frontend: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(o.fe_args[0]))); + rc = 1; + goto done; + } + } + + code.opt_level = o.check_only ? 0 : o.opt_level; + code.debug_info = o.debug_info; + code.check_only = o.check_only ? true : false; + code.emit_asm_source = (o.emit == COMPILE_EMIT_ASM); + code.emit_c_source = (o.emit == COMPILE_EMIT_C); + code.emit_ir = (o.emit == COMPILE_EMIT_IR); + diag.warnings_are_errors = o.warnings_are_errors; + diag.max_errors = o.max_errors; + driver_cflags_fill_pp(&o.cf, &pp); + + rc = 0; + for (i = 0; i < o.nsources && rc == 0; ++i) { + const char* src = o.sources[i]; + char* owned = NULL; + size_t owned_size = 0; + const char* out_path = NULL; + if (!o.check_only) { + if (o.output_path) { + out_path = o.output_path; + } else { + owned = compile_default_out(&env, &o, src, &owned_size); + if (!owned) { + driver_errf(COMPILE_TOOL, "out of memory"); + rc = 1; + break; + } + out_path = owned; + } + } + rc = compile_one(&ctx, compiler, lang, &code, &diag, + caps.preprocessor ? &pp : NULL, lang_extra, src, out_path); + if (owned) driver_free(&env, owned, owned_size); + } + +done: + if (lang_extra) kit_frontend_free_options(compiler, lang, lang_extra); + if (compiler) driver_compiler_free(compiler); + if (runtime_resolved) driver_runtime_support_fini(&env, &runtime); + driver_cflags_fini(&o.cf, &env); +done_early: + if (o.sources) driver_free(&env, o.sources, (size_t)argc * sizeof(*o.sources)); + if (o.fe_args) driver_free(&env, o.fe_args, (size_t)argc * sizeof(*o.fe_args)); + driver_env_fini(&env); + return rc; +} diff --git a/driver/cmd/dbg.c b/driver/cmd/dbg.c @@ -319,22 +319,24 @@ static int dbg_compile_and_jit(DbgOpts* o, KitCompiler* compiler, KitCCompileOptions z = {0}; copts = z; } + KitPreprocessOptions pp; copts.code.opt_level = o->opt_level; copts.code.debug_info = o->debug_info; - driver_cflags_fill_pp(&o->cf, &copts.preprocess); - return driver_inputs_compile_and_jit(&o->inputs, compiler, host, &copts, + driver_cflags_fill_pp(&o->cf, &pp); + return driver_inputs_compile_and_jit(&o->inputs, compiler, host, &copts, &pp, link_entry, driver_dlsym_resolver, NULL, out_jit); } -static void dbg_fill_compile_options(DbgOpts* o, KitCCompileOptions* copts) { +static void dbg_fill_compile_options(DbgOpts* o, KitCCompileOptions* copts, + KitPreprocessOptions* pp) { { KitCCompileOptions z = {0}; *copts = z; } copts->code.opt_level = o->opt_level; copts->code.debug_info = o->debug_info; - driver_cflags_fill_pp(&o->cf, &copts->preprocess); + driver_cflags_fill_pp(&o->cf, pp); } /* ============================================================ @@ -380,6 +382,7 @@ typedef struct DbgState { KitCompiler* compiler; KitContext ctx; KitCCompileOptions copts; + KitPreprocessOptions pp; /* preprocessor settings for REPL compiles */ KitJit* jit; KitJitSession* session; const KitObjFile* view; @@ -1885,7 +1888,7 @@ static KitStatus dbg_compile_session_for(DbgState* s, KitLanguage lang, sopts.lang = lang; sopts.compile.code = s->copts.code; sopts.compile.diagnostics = s->copts.diagnostics; - sopts.compile.language_options = &s->copts; + sopts.compile.preprocess = s->pp; st = kit_compile_session_new(s->compiler, &sopts, &s->compile_sessions[lang]); if (st != KIT_OK) return st; *out = s->compile_sessions[lang]; @@ -2113,7 +2116,7 @@ static void dbg_cmd_language(DbgState* s, const char* rest) { while (*p && dbg_isspace((unsigned char)*p)) ++p; while (p[n] && !dbg_isspace((unsigned char)p[n])) ++n; if (n == 0) { - const KitPreprocessOptions* pp = &s->copts.preprocess; + const KitPreprocessOptions* pp = &s->pp; driver_printf("Language: %.*s\n", KIT_SLICE_ARG(kit_slice_cstr( dbg_jit_language_name(s->default_jit_lang)))); @@ -3418,7 +3421,7 @@ int driver_dbg(int argc, char** argv) { st.env = &env; st.compiler = compiler; st.ctx = ctx; - dbg_fill_compile_options(&o, &st.copts); + 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); diff --git a/driver/cmd/run.c b/driver/cmd/run.c @@ -646,7 +646,6 @@ static void run_fill_compile_opts(const RunOptions* o, * optimizer runs and each function is captured into the InterpProgram. */ copts->code.opt_level = (o->no_jit && o->opt_level < 1) ? 1 : o->opt_level; copts->code.debug_info = o->debug_info; - driver_cflags_fill_pp(&o->cf, &copts->preprocess); copts->diagnostics.warnings_are_errors = o->warnings_are_errors; copts->diagnostics.max_errors = o->max_errors; } @@ -658,8 +657,10 @@ static void run_fill_compile_opts(const RunOptions* o, static int run_compile_and_jit(RunOptions* o, KitCompiler* compiler, const KitJitHost* host, KitJit** out_jit) { KitCCompileOptions copts; + KitPreprocessOptions pp; run_fill_compile_opts(o, &copts); - return driver_inputs_compile_and_jit(&o->inputs, compiler, host, &copts, + driver_cflags_fill_pp(&o->cf, &pp); + return driver_inputs_compile_and_jit(&o->inputs, compiler, host, &copts, &pp, o->entry, driver_dlsym_resolver, NULL, out_jit); } diff --git a/driver/driver.h b/driver/driver.h @@ -38,6 +38,7 @@ int driver_main(int argc, char** argv); /* Direct entry per tool. Each lives in driver/<tool>.c. */ int driver_cc(int argc, char** argv); int driver_check(int argc, char** argv); +int driver_compile(int argc, char** argv); int driver_install(int argc, char** argv); int driver_cpp(int argc, char** argv); int driver_as(int argc, char** argv); @@ -76,6 +77,7 @@ int driver_mc(int argc, char** argv); * headers — only --help triggers help there). */ void driver_help_cc(void); void driver_help_check(void); +void driver_help_compile(void); void driver_help_install(void); void driver_help_cpp(void); void driver_help_as(void); diff --git a/driver/lib/compile_engine.c b/driver/lib/compile_engine.c @@ -0,0 +1,60 @@ +#include "compile_engine.h" + +#include <kit/asm_emit.h> +#include <string.h> + +KitStatus driver_compile_run(KitCompiler* compiler, KitLanguage lang, + const KitCodeOptions* code, + const KitDiagnosticOptions* diagnostics, + const KitPreprocessOptions* pp, + const void* lang_extra, KitSlice name, + const KitSlice* bytes, KitWriter* emit_out, + KitObjBuilder** obj_out) { + KitCompileSessionOptions sopts; + KitCompileSession* session = NULL; + KitSourceInput sin; + KitObjBuilder* ob = NULL; + KitCodeOptions code_copy = *code; + KitStatus st; + + if (obj_out) *obj_out = NULL; + + /* For the in-CG emit modes the output writer is consumed during codegen, so + * wire it onto the code options before the session runs. */ + if (emit_out && code_copy.emit_c_source) code_copy.c_source_writer = emit_out; + if (emit_out && code_copy.emit_ir) code_copy.ir_dump_writer = emit_out; + + memset(&sopts, 0, sizeof(sopts)); + sopts.lang = lang; + sopts.compile.code = code_copy; + sopts.compile.diagnostics = *diagnostics; + if (pp) sopts.compile.preprocess = *pp; + sopts.compile.language_options = lang_extra; + + memset(&sin, 0, sizeof(sin)); + sin.name = name; + sin.bytes = *bytes; + sin.lang = lang; + + st = kit_compile_session_new(compiler, &sopts, &session); + if (st == KIT_OK) st = kit_compile_session_compile(session, &sin, &ob); + kit_compile_session_free(session); + if (st != KIT_OK) return st; + + if (obj_out) { + *obj_out = ob; + return KIT_OK; + } + + /* emit_out path: serialize by output mode. The in-CG modes already wrote + * through the wired writer above. */ + if (code_copy.emit_c_source || code_copy.emit_ir) { + /* nothing to serialize here */ + } else if (code_copy.emit_asm_source) { + st = kit_obj_builder_emit_asm(ob, emit_out); + } else { + st = kit_obj_builder_emit(ob, emit_out); + } + kit_obj_builder_free(ob); + return st; +} diff --git a/driver/lib/compile_engine.h b/driver/lib/compile_engine.h @@ -0,0 +1,35 @@ +#ifndef KIT_DRIVER_COMPILE_ENGINE_H +#define KIT_DRIVER_COMPILE_ENGINE_H + +#include <kit/compile.h> +#include <kit/object.h> +#include <kit/preprocess.h> + +/* Language-neutral "compile one source" step shared by `cc` and `compile`. + * + * Builds a KitCompileSession for `lang`, compiles `bytes` (labelled `name`), + * and then either: + * - returns the object builder via `obj_out` (caller owns it; used by the + * link and check paths), or + * - emits the result to `emit_out`, routed by code->emit_*: object bytes by + * default, `.s` assembly for emit_asm_source, or — for emit_c_source / + * emit_ir — the writer is wired onto the CG and the builder is dropped. + * Exactly one of `emit_out` / `obj_out` must be non-NULL. + * + * code, diagnostics : common per-compile settings (required). + * pp : preprocessor settings, applied to preprocessor-enabled + * frontends; NULL for none. + * lang_extra : opaque per-frontend options for language_options + * (e.g. KitWasmCompileOptions*); NULL when the frontend + * has none. + * + * Returns the compile/emit KitStatus. */ +KitStatus driver_compile_run(KitCompiler* compiler, KitLanguage lang, + const KitCodeOptions* code, + const KitDiagnosticOptions* diagnostics, + const KitPreprocessOptions* pp, + const void* lang_extra, KitSlice name, + const KitSlice* bytes, KitWriter* emit_out, + KitObjBuilder** obj_out); + +#endif diff --git a/driver/lib/inputs.c b/driver/lib/inputs.c @@ -96,6 +96,7 @@ const char* driver_inputs_first_name(const DriverInputs* in) { int driver_inputs_compile_and_jit(DriverInputs* in, KitCompiler* compiler, const KitJitHost* host, const KitCCompileOptions* copts, + const KitPreprocessOptions* pp, const char* entry, void* (*extern_resolver)(void*, KitSlice), void* extern_resolver_user, @@ -170,18 +171,11 @@ int driver_inputs_compile_and_jit(DriverInputs* in, KitCompiler* compiler, KitCompileSession* session = NULL; KitSourceInput sin; KitStatus st; - KitAsmCompileOptions aopts = {0}; memset(&sopts, 0, sizeof(sopts)); sopts.lang = lang; sopts.compile.code = copts->code; sopts.compile.diagnostics = copts->diagnostics; - if (lang == KIT_LANG_ASM) { - aopts.code = copts->code; - aopts.diagnostics = copts->diagnostics; - sopts.compile.language_options = &aopts; - } else { - sopts.compile.language_options = copts; - } + if (pp) sopts.compile.preprocess = *pp; memset(&sin, 0, sizeof(sin)); sin.name = kit_slice_cstr(in->sources[i]); sin.bytes = src_bytes[i]; @@ -199,7 +193,7 @@ int driver_inputs_compile_and_jit(DriverInputs* in, KitCompiler* compiler, sopts.lang = in->source_memory[i].lang; sopts.compile.code = copts->code; sopts.compile.diagnostics = copts->diagnostics; - sopts.compile.language_options = copts; + if (pp) sopts.compile.preprocess = *pp; st = kit_compile_session_new(compiler, &sopts, &session); if (st == KIT_OK) st = kit_compile_session_compile(session, &in->source_memory[i], diff --git a/driver/lib/inputs.h b/driver/lib/inputs.h @@ -22,7 +22,7 @@ * ... handle other flags / positionals ... * } * if (driver_inputs_count(&in) == 0) { ... no inputs ... } - * rc = driver_inputs_compile_and_jit(&in, compiler, host, &copts, entry, + * rc = driver_inputs_compile_and_jit(&in, compiler, host, &copts, &pp, entry, * driver_dlsym_resolver, NULL, &jit); * driver_inputs_release(&in); * @@ -81,13 +81,16 @@ uint32_t driver_inputs_count(const DriverInputs*); const char* driver_inputs_first_name(const DriverInputs*); /* Load every input through env.file_io, compile sources via `compiler` - * with `copts`, and JIT-link the result. `host` carries execmem/tls; - * `extern_resolver` populates the JIT link options. On success *out_jit - * owns the JIT image; on failure an error has already been reported. - * Returns 0 on success, 1 otherwise. */ + * with `copts` (code/diagnostics) and `pp` (preprocessor settings, applied to + * preprocessor-enabled frontends), and JIT-link the result. `host` carries + * execmem/tls; `extern_resolver` populates the JIT link options. `pp` may be + * NULL for no preprocessor configuration. On success *out_jit owns the JIT + * image; on failure an error has already been reported. Returns 0 on success, + * 1 otherwise. */ int driver_inputs_compile_and_jit(DriverInputs*, KitCompiler*, const KitJitHost*, const KitCCompileOptions* copts, + const KitPreprocessOptions* pp, const char* entry, void* (*extern_resolver)(void*, KitSlice), void* extern_resolver_user, KitJit** out_jit); diff --git a/driver/lib/runtime.c b/driver/lib/runtime.c @@ -534,6 +534,7 @@ static int rt_compile_source(DriverEnv* env, } } else { KitCCompileOptions copts = {0}; + KitPreprocessOptions pp = {0}; KitCompileSessionOptions sopts; KitCompileSession* session = NULL; KitSourceInput sin; @@ -541,14 +542,14 @@ static int rt_compile_source(DriverEnv* env, char** owned_dirs = NULL; size_t* owned_sizes = NULL; uint32_t owned_count = 0; - if (rt_prepare_pp(env, support, variant, 0, &copts.preprocess, &owned_dirs, - &owned_sizes, &owned_count) != 0) + if (rt_prepare_pp(env, support, variant, 0, &pp, &owned_dirs, &owned_sizes, + &owned_count) != 0) goto out; memset(&sopts, 0, sizeof(sopts)); sopts.lang = KIT_LANG_C; sopts.compile.code = copts.code; sopts.compile.diagnostics = copts.diagnostics; - sopts.compile.language_options = &copts; + sopts.compile.preprocess = pp; memset(&sin, 0, sizeof(sin)); sin.name = kit_slice_cstr(src_path); sin.bytes = input; @@ -558,7 +559,7 @@ static int rt_compile_source(DriverEnv* env, if (st == KIT_OK) st = kit_obj_builder_emit(ob, writer); kit_obj_builder_free(ob); kit_compile_session_free(session); - rt_free_pp(env, &copts.preprocess, owned_dirs, owned_sizes, owned_count); + rt_free_pp(env, &pp, owned_dirs, owned_sizes, owned_count); } if (st != KIT_OK || kit_writer_status(writer) != KIT_OK) { driver_errf(tool, "failed to build runtime source: %.*s", diff --git a/driver/main.c b/driver/main.c @@ -32,6 +32,11 @@ static const DriverToolDesc driver_tools[] = { {"check", driver_check, driver_help_check, "Run C frontend checks without emitting code", DRIVER_GROUP_OTHER}, #endif +#if KIT_TOOL_COMPILE_ENABLED + {"compile", driver_compile, driver_help_compile, + "Compile one frontend's sources to objects / asm / C / IR (no link)", + DRIVER_GROUP_OTHER}, +#endif #if KIT_TOOL_INSTALL_ENABLED {"install", driver_install, driver_help_install, "Symlink the kit tools into a dir for drop-in toolchain use", diff --git a/include/kit/compile.h b/include/kit/compile.h @@ -31,9 +31,13 @@ typedef struct KitDiagnosticOptions { uint32_t max_errors; /* 0 means unlimited */ } KitDiagnosticOptions; +/* Per-language compile options. Preprocessor settings are NOT here: they live + * on the common KitFrontendCompileOptions, gated by a frontend's + * KitFrontendCaps.preprocessor capability, so the generic compile driver needs + * no per-language special-casing. These structs carry only what a frontend + * reads from `language_options` beyond the common code/diagnostics. */ typedef struct KitCCompileOptions { KitCodeOptions code; - KitPreprocessOptions preprocess; KitDiagnosticOptions diagnostics; } KitCCompileOptions; @@ -42,6 +46,26 @@ typedef struct KitAsmCompileOptions { KitDiagnosticOptions diagnostics; } KitAsmCompileOptions; +/* WebAssembly proposal features the frontend accepts and validates against. + * Bit values match the frontend's internal feature set. */ +typedef enum KitWasmFeature { + KIT_WASM_FEATURE_THREADS = 1u << 0, + KIT_WASM_FEATURE_TYPED_FUNC_REFS = 1u << 1, + KIT_WASM_FEATURE_TAIL_CALLS = 1u << 2, + KIT_WASM_FEATURE_MULTI_MEMORY = 1u << 3, + KIT_WASM_FEATURE_MEMORY64 = 1u << 4, + KIT_WASM_FEATURE_BULK_MEMORY = 1u << 5, + KIT_WASM_FEATURE_NONTRAPPING_FTOI = 1u << 6, +} KitWasmFeature; + +/* Per-language extras for the wasm frontend, planted in + * KitFrontendCompileOptions.language_options. `features` is a bitset of + * KitWasmFeature. When language_options is NULL the frontend enables its full + * default feature set (every KitWasmFeature). */ +typedef struct KitWasmCompileOptions { + uint32_t features; +} KitWasmCompileOptions; + typedef enum KitFrontendInputKind { KIT_FRONTEND_INPUT_TRANSLATION_UNIT = 0, KIT_FRONTEND_INPUT_REPL_TOPLEVEL = 1, @@ -60,6 +84,11 @@ typedef struct KitSourceInput { typedef struct KitFrontendCompileOptions { KitCodeOptions code; KitDiagnosticOptions diagnostics; + /* Preprocessor configuration. Meaningful only for frontends whose + * KitFrontendCaps.preprocessor is true (C today); ignored by the rest. */ + KitPreprocessOptions preprocess; + /* Opaque per-frontend extras (e.g. KitWasmCompileOptions). Produced by the + * frontend's parse_options hook; NULL when the frontend has none. */ const void* language_options; KitFrontendInputKind input_kind; KitSlice repl_entry_name; @@ -79,6 +108,24 @@ typedef void (*KitFrontendFreeFn)(KitFrontendState*); typedef void (*KitFrontendCommitFn)(KitFrontendState*); typedef void (*KitFrontendAbortFn)(KitFrontendState*); +/* Static per-frontend capability descriptor (a struct so it can grow without + * an ABI break). A generic driver consults it to decide which common options + * apply to the resolved language — e.g. whether the preprocessor flag family + * (-I/-isystem/-D/-U) is accepted. */ +typedef struct KitFrontendCaps { + bool preprocessor; /* honors KitFrontendCompileOptions.preprocess */ +} KitFrontendCaps; + +/* 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 + * compiler's KitContext heap and released by free_options. argv holds only the + * leftover flags (no program name). Returns KIT_INVALID (after emitting a + * diagnostic) on an unrecognized flag. */ +typedef KitStatus (*KitFrontendParseOptionsFn)(KitCompiler*, int argc, + char** argv, void** out_opts); +typedef void (*KitFrontendFreeOptionsFn)(KitCompiler*, void* opts); + typedef struct KitFrontendVTable { KitFrontendNewFn new_frontend; KitFrontendCompileFn compile; @@ -106,6 +153,14 @@ typedef struct KitFrontendVTable { * caller has linked and published the object. */ KitFrontendCommitFn commit; KitFrontendAbortFn abort; + + /* Capabilities and optional frontend-specific flag parsing. `caps` is read + * by value (no instance needed). `parse_options`/`free_options` may be NULL + * for frontends with no extra flags; a generic driver then rejects any + * leftover flags for that language. */ + KitFrontendCaps caps; + KitFrontendParseOptionsFn parse_options; + KitFrontendFreeOptionsFn free_options; } KitFrontendVTable; /* Map a path to a language purely by its registered extension. Walks every @@ -117,6 +172,19 @@ KIT_API KitLanguage kit_language_for_path(KitCompiler*, const char* path); KIT_API KitStatus kit_register_frontend(KitCompiler*, KitLanguage, const KitFrontendVTable*); +/* Query a registered frontend's static capabilities. Returns KIT_INVALID if no + * frontend is registered for `lang`. */ +KIT_API KitStatus kit_frontend_caps(KitCompiler*, KitLanguage, + KitFrontendCaps* out); +/* Parse `lang`'s frontend-specific flags into an opaque options blob (see + * KitFrontendParseOptionsFn). Returns KIT_INVALID if the frontend has no + * parser (any leftover flag is then an error) or rejects a flag. On success + * *out_opts is non-NULL and must be released with kit_frontend_free_options. */ +KIT_API KitStatus kit_frontend_parse_options(KitCompiler*, KitLanguage, + int argc, char** argv, + void** out_opts); +KIT_API void kit_frontend_free_options(KitCompiler*, KitLanguage, void* opts); + KIT_API uint32_t kit_compiler_arch_predefines(KitCompiler*, const KitPredefinedMacro** out); diff --git a/include/kit/config.h b/include/kit/config.h @@ -103,6 +103,7 @@ * the driver/<tool>.c objects included in the kit binary. */ #define KIT_TOOL_CC_ENABLED 1 #define KIT_TOOL_CHECK_ENABLED 1 +#define KIT_TOOL_COMPILE_ENABLED 1 #define KIT_TOOL_INSTALL_ENABLED 1 #define KIT_TOOL_CPP_ENABLED 1 #define KIT_TOOL_AS_ENABLED 1 diff --git a/lang/c/c.c b/lang/c/c.c @@ -58,9 +58,8 @@ static KitStatus c_frontend_compile(KitFrontendState* frontend, KitObjBuilder* out) { CFrontend* fe = (CFrontend*)frontend; KitCompiler* c; - /* The libkit pipeline plants the original KitCCompileOptions* in - * language_options. Recover it for preprocessor configuration. */ - const KitCCompileOptions* opts; + /* Code, diagnostics, and preprocessor settings all arrive on the common + * KitFrontendCompileOptions; the C frontend uses no language_options. */ const KitSlice* bytes; Pool* pool; Lexer* lex; @@ -72,8 +71,6 @@ static KitStatus c_frontend_compile(KitFrontendState* frontend, if (!fe || !fe->c) return KIT_INVALID; c = fe->c; if (!fe_opts || !input) c_bad_options(c, "compile args missing"); - opts = (const KitCCompileOptions*)fe_opts->language_options; - if (!opts) c_bad_options(c, "C frontend missing KitCCompileOptions"); bytes = &input->bytes; kit_frontend_metrics_scope_begin(c, "compile.c.setup"); @@ -102,21 +99,21 @@ static KitStatus c_frontend_compile(KitFrontendState* frontend, kit_frontend_metrics_scope_begin(c, "compile.c.pp_options"); kit_frontend_metrics_count(c, "compile.c.pp_include_dirs", - opts->preprocess.ninclude_dirs); + fe_opts->preprocess.ninclude_dirs); kit_frontend_metrics_count(c, "compile.c.pp_system_include_dirs", - opts->preprocess.nsystem_include_dirs); + fe_opts->preprocess.nsystem_include_dirs); kit_frontend_metrics_count(c, "compile.c.pp_defines", - opts->preprocess.ndefines); + fe_opts->preprocess.ndefines); kit_frontend_metrics_count(c, "compile.c.pp_undefines", - opts->preprocess.nundefines); - c_apply_pp_options(pp, &opts->preprocess); + fe_opts->preprocess.nundefines); + c_apply_pp_options(pp, &fe_opts->preprocess); kit_frontend_metrics_scope_end(c, "compile.c.pp_options"); kit_frontend_metrics_scope_begin(c, "compile.c.pp_push_input"); pp_push_input(pp, lex); kit_frontend_metrics_scope_end(c, "compile.c.pp_push_input"); kit_frontend_metrics_scope_begin(c, "compile.c.parse_codegen"); - parse_c(c, pool, pp, decls, cg, (KitSymVis)opts->code.default_visibility); + parse_c(c, pool, pp, decls, cg, (KitSymVis)fe_opts->code.default_visibility); kit_frontend_metrics_scope_end(c, "compile.c.parse_codegen"); kit_frontend_metrics_scope_begin(c, "compile.c.cleanup"); @@ -150,4 +147,7 @@ const KitFrontendVTable kit_c_frontend_vtable = { /* commit/abort: C has no durable cross-compile state yet */ NULL, NULL, + {true}, /* caps: C honors the common preprocess options (-I/-D/-U/...) */ + NULL, /* parse_options: C has no frontend-specific flags */ + NULL, /* free_options */ }; diff --git a/lang/toy/compile.c b/lang/toy/compile.c @@ -244,4 +244,7 @@ const KitFrontendVTable kit_toy_frontend_vtable = { (uint32_t)(sizeof toy_extensions / sizeof toy_extensions[0]), toy_frontend_commit, toy_frontend_abort, + {false}, /* caps: toy has no preprocessor */ + NULL, /* parse_options: no toy-specific flags yet */ + NULL, /* free_options */ }; diff --git a/lang/wasm/wasm.c b/lang/wasm/wasm.c @@ -1,5 +1,16 @@ +#include <stdarg.h> +#include <string.h> + #include "wasm/wasm.h" +/* Every KitWasmFeature bit — the frontend's default when no -mfeature flags + * narrow it (and what wasm_module_init seeds, kept in sync). */ +#define WASM_FEATURES_ALL \ + (KIT_WASM_FEATURE_THREADS | KIT_WASM_FEATURE_TYPED_FUNC_REFS | \ + KIT_WASM_FEATURE_TAIL_CALLS | KIT_WASM_FEATURE_MULTI_MEMORY | \ + KIT_WASM_FEATURE_MEMORY64 | KIT_WASM_FEATURE_BULK_MEMORY | \ + KIT_WASM_FEATURE_NONTRAPPING_FTOI) + static void wasm_parse_any(KitCompiler* c, KitSlice name, const KitSlice* input, WasmModule* m) { if (wasm_is_binary(input)) @@ -24,6 +35,91 @@ static KitFrontendState* wasm_frontend_new(KitCompiler* c) { return (KitFrontendState*)fe; } +static void wasm_opt_errf(KitCompiler* c, const char* fmt, ...) { + const KitContext* ctx = kit_compiler_context(c); + va_list ap; + KitSrcLoc loc; + if (!ctx || !ctx->diag || !ctx->diag->emit) return; + loc.file_id = 0; + loc.line = 0; + loc.col = 0; + va_start(ap, fmt); + ctx->diag->emit(ctx->diag, KIT_DIAG_ERROR, loc, fmt, ap); + va_end(ap); +} + +/* Map a feature name (`tail-calls`, `memory64`, ...) to its KitWasmFeature + * bit. Returns 0 for an unknown name. */ +static uint32_t wasm_feature_bit(const char* name) { + static const struct { + const char* name; + uint32_t bit; + } table[] = { + {"threads", KIT_WASM_FEATURE_THREADS}, + {"typed-func-refs", KIT_WASM_FEATURE_TYPED_FUNC_REFS}, + {"tail-calls", KIT_WASM_FEATURE_TAIL_CALLS}, + {"multi-memory", KIT_WASM_FEATURE_MULTI_MEMORY}, + {"memory64", KIT_WASM_FEATURE_MEMORY64}, + {"bulk-memory", KIT_WASM_FEATURE_BULK_MEMORY}, + {"nontrapping-ftoi", KIT_WASM_FEATURE_NONTRAPPING_FTOI}, + }; + size_t i; + for (i = 0; i < sizeof table / sizeof table[0]; ++i) { + if (strcmp(name, table[i].name) == 0) return table[i].bit; + } + return 0; +} + +/* Parse the wasm frontend flags `kit compile` did not consume: + * -mfeature=NAME enable a proposal feature + * -mno-feature=NAME disable one + * Repeatable; starts from the full default set. */ +static KitStatus wasm_parse_options(KitCompiler* c, int argc, char** argv, + void** out_opts) { + KitHeap* h = kit_compiler_context(c)->heap; + KitWasmCompileOptions* o; + int i; + *out_opts = NULL; + o = (KitWasmCompileOptions*)h->alloc(h, sizeof(*o), + _Alignof(KitWasmCompileOptions)); + if (!o) return KIT_NOMEM; + o->features = WASM_FEATURES_ALL; + for (i = 0; i < argc; ++i) { + const char* a = argv[i]; + const char* name; + int enable; + uint32_t bit; + if (strncmp(a, "-mfeature=", 10) == 0) { + enable = 1; + name = a + 10; + } else if (strncmp(a, "-mno-feature=", 13) == 0) { + enable = 0; + name = a + 13; + } else { + wasm_opt_errf(c, "wasm: unknown option: %s", a); + h->free(h, o, sizeof(*o)); + return KIT_INVALID; + } + bit = wasm_feature_bit(name); + if (!bit) { + wasm_opt_errf(c, "wasm: unknown feature: %s", name); + h->free(h, o, sizeof(*o)); + return KIT_INVALID; + } + if (enable) + o->features |= bit; + else + o->features &= ~bit; + } + *out_opts = o; + return KIT_OK; +} + +static void wasm_free_options(KitCompiler* c, void* opts) { + KitHeap* h = kit_compiler_context(c)->heap; + if (opts) h->free(h, opts, sizeof(KitWasmCompileOptions)); +} + static KitStatus wasm_frontend_compile(KitFrontendState* frontend, const KitFrontendCompileOptions* opts, const KitSourceInput* input, @@ -31,10 +127,14 @@ static KitStatus wasm_frontend_compile(KitFrontendState* frontend, WasmFrontend* fe = (WasmFrontend*)frontend; KitCompiler* c; WasmModule m; + const KitWasmCompileOptions* wopts; if (!fe || !fe->c || !opts || !input || !out) return KIT_INVALID; c = fe->c; - (void)opts->language_options; /* wasm frontend has no per-language options */ + wopts = (const KitWasmCompileOptions*)opts->language_options; wasm_module_init(&m, kit_compiler_context(c)->heap); + /* wasm_module_init seeds the full feature set; narrow it when the driver + * supplied parsed options. NULL keeps the default (run/dbg/cc paths). */ + if (wopts) m.features = wopts->features; wasm_parse_any(c, input->name, &input->bytes, &m); wasm_emit_cg(c, &opts->code, out, &m); wasm_module_free(&m); @@ -58,8 +158,11 @@ const KitFrontendVTable kit_wasm_frontend_vtable = { wasm_frontend_free, wasm_extensions, (uint32_t)(sizeof wasm_extensions / sizeof wasm_extensions[0]), - NULL, /* commit: wasm has no durable cross-compile state */ - NULL, /* abort */ + NULL, /* commit: wasm has no durable cross-compile state */ + NULL, /* abort */ + {false}, /* caps: wasm has no preprocessor */ + wasm_parse_options, + wasm_free_options, }; KIT_API int kit_wasm_wat_to_wasm(KitCompiler* c, const KitSlice* input, diff --git a/src/api/compile.c b/src/api/compile.c @@ -47,8 +47,11 @@ const KitFrontendVTable kit_asm_frontend_vtable = { asm_frontend_free, asm_extensions, (uint32_t)(sizeof asm_extensions / sizeof asm_extensions[0]), - NULL, /* commit: asm has no durable cross-compile state */ - NULL, /* abort */ + NULL, /* commit: asm has no durable cross-compile state */ + NULL, /* abort */ + {false}, /* caps: raw asm, no preprocessor (.S cpp is a driver concern) */ + NULL, /* parse_options: no asm-specific flags */ + NULL, /* free_options */ }; static SrcLoc no_loc(void) { @@ -130,6 +133,33 @@ KitStatus kit_register_frontend(KitCompiler* c, KitLanguage lang, return KIT_OK; } +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 (!v) return KIT_INVALID; + *out = v->caps; + return KIT_OK; +} + +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 (!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 (v && v->free_options) v->free_options(c, opts); +} + uint32_t kit_compiler_arch_predefines(KitCompiler* c, const KitPredefinedMacro** out) { const ArchImpl* arch; diff --git a/test/compile/run.sh b/test/compile/run.sh @@ -0,0 +1,96 @@ +#!/bin/sh +# Driver-level checks for `kit compile` — the kit-native, single-language +# source compiler. Self-checking (no golden files): we assert exit status, +# output existence, and a few symbol/text markers via the shared kit_* verbs. +# +# Coverage: per-language compile (C / toy / wasm), the emit modes +# (-S / --emit=c / --emit=ir), check-only, default output naming, the +# frontend-owned wasm feature flags, and the negative paths that exercise the +# capability gate, the frontend option parser, and the no-link policy. + +set -u + +script_dir=$(cd "$(dirname "$0")" && pwd) +repo_root=$(cd "$script_dir/../.." && pwd) + +KIT="${KIT:-$repo_root/build/kit}" + +if [ ! -x "$KIT" ]; then + echo "compile: kit binary not found at $KIT" >&2 + exit 2 +fi + +work=$(mktemp -d "${TMPDIR:-/tmp}/kit-compile-test.XXXXXX") +trap 'rm -rf "$work"' EXIT + +KIT_KIT_DIR="$repo_root/test/lib" +. "$repo_root/test/lib/kit_sh_kit.sh" +kit_report_init + +# ---- fixtures (all local to $work so default-name tests are isolated) ------- +mkdir -p "$work/inc" +printf '#define ZERO 0\n' > "$work/inc/h.h" +printf '#include "h.h"\nint main(void){return ZERO;}\n' > "$work/hello.c" +printf '#include <stdint.h>\nint32_t f(void){return 1;}\n' > "$work/std.c" +cp "$(ls "$repo_root"/test/toy/cases/*.toy | head -1)" "$work/prog.toy" +cp "$repo_root/test/wasm/cases/if_return.wat" "$work/prog.wat" + +cd "$work" + +# ---- C: preprocessor flags + object output --------------------------------- +run_ok c-compile-obj "$KIT" compile -c -Iinc -DUNUSED=1 hello.c -o hello.o +assert_file_exists c-obj-exists hello.o +"$KIT" nm hello.o > nm.out 2>/dev/null +contains c-obj-has-main nm.out main + +# C freestanding system header (<stdint.h>) resolves via the rt include set. +run_ok c-freestanding-header "$KIT" compile -c std.c -o std.o + +# Default output name: <basename>.o next to the cwd when -o is omitted. +run_ok c-default-name "$KIT" compile -c -Iinc hello.c +assert_file_exists c-default-name-file hello.o + +# ---- toy + wasm: non-C frontends through the generic driver ----------------- +run_ok toy-compile "$KIT" compile -c prog.toy -o prog_toy.o +assert_file_exists toy-obj-exists prog_toy.o + +run_ok wasm-compile "$KIT" compile -c prog.wat -o prog_wat.o +assert_file_exists wasm-obj-exists prog_wat.o + +# Frontend-owned option: the wasm parser accepts -m[no-]feature. +run_ok wasm-feature-flag "$KIT" compile -c -mno-feature=tail-calls prog.wat -o prog_wat2.o + +# ---- emit modes ------------------------------------------------------------ +run_ok emit-asm "$KIT" compile -S -Iinc hello.c -o hello.s +contains emit-asm-text hello.s .text +run_ok emit-c "$KIT" compile --emit=c prog.toy -o prog.c +assert_file_exists emit-c-file prog.c +run_ok emit-ir "$KIT" compile --emit=ir -O1 prog.toy -o prog.ir +assert_file_exists emit-ir-file prog.ir + +# ---- check-only writes no object ------------------------------------------- +rm -f check.o +run_ok check-only "$KIT" compile -fsyntax-only -Iinc hello.c +if [ ! -f check.o ] && [ ! -f hello_check.o ]; then ok check-only-no-output +else not_ok check-only-no-output; fi + +# ---- negative paths -------------------------------------------------------- +# Preprocessor flag on a non-preprocessor frontend is rejected by the gate. +run_fail neg-cpp-on-wasm "$KIT" compile -Iinc -c prog.wat -o x.o +# A frontend with no option parser rejects any leftover flag. +run_fail neg-unknown-toy-flag "$KIT" compile --bogus -c prog.toy -o x.o +# The wasm parser rejects an unknown feature name. +run_fail neg-bad-wasm-feature "$KIT" compile -mfeature=nope -c prog.wat -o x.o +# compile never links: object/archive inputs are refused. +run_fail neg-link-input "$KIT" compile hello.o +# One language per invocation. +run_fail neg-mixed-language "$KIT" compile prog.toy prog.wat +# --emit=c needs an explicit destination. +run_fail neg-emit-c-needs-o "$KIT" compile --emit=c prog.toy +# IR is only available with the optimizer on. +run_fail neg-ir-needs-opt "$KIT" compile --emit=ir prog.toy -o x.ir +# -o cannot fan out across multiple sources. +run_fail neg-o-multi "$KIT" compile -c hello.c std.c -o both.o + +kit_summary compile-driver +kit_exit diff --git a/test/dbg/cases/toy-local-inspection/expected b/test/dbg/cases/toy-local-inspection/expected @@ -1,6 +1,6 @@ kit dbg — 'h' for help, 'q' to quit Breakpoint 1 at 0xADDR (@CASE@/main.toy:3) -Breakpoint 1 (@CASE@/main.toy:3) hit at 0xADDR <helper+0x78> at @CASE@/main.toy:3:10 +Breakpoint 1 (@CASE@/main.toy:3) hit at 0xADDR <helper+0x74> at @CASE@/main.toy:3:10 1 fn helper(v: i64): i64 { 2 let n: i64 = v + 2; 3 > return n; diff --git a/test/parse/harness/parse_runner.c b/test/parse/harness/parse_runner.c @@ -320,10 +320,10 @@ static int opt_level_from_env(void) { exit(2); } -static void add_test_system_includes(KitCCompileOptions* opts) { +static void add_test_system_includes(KitPreprocessOptions* pp) { static const char* dirs[] = {"rt/include"}; - opts->preprocess.system_include_dirs = dirs; - opts->preprocess.nsystem_include_dirs = 1; + pp->system_include_dirs = dirs; + pp->nsystem_include_dirs = 1; } static int add_runtime_archive(KitLinkArchiveInput* rt_archive, @@ -347,8 +347,8 @@ static int add_runtime_archive(KitLinkArchiveInput* rt_archive, } static KitStatus compile_c_obj(KitCompiler* c, const KitCCompileOptions* opts, - KitSlice name, const KitSlice* in, - KitObjBuilder** out) { + const KitPreprocessOptions* pp, KitSlice name, + const KitSlice* in, KitObjBuilder** out) { KitCompileSessionOptions sopts; KitCompileSession* session = NULL; KitSourceInput sin; @@ -357,7 +357,7 @@ static KitStatus compile_c_obj(KitCompiler* c, const KitCCompileOptions* opts, sopts.lang = KIT_LANG_C; sopts.compile.code = opts->code; sopts.compile.diagnostics = opts->diagnostics; - sopts.compile.language_options = opts; + if (pp) sopts.compile.preprocess = *pp; memset(&sin, 0, sizeof(sin)); sin.name = name; sin.bytes = *in; @@ -369,10 +369,10 @@ static KitStatus compile_c_obj(KitCompiler* c, const KitCCompileOptions* opts, } static KitStatus compile_c_emit(KitCompiler* c, const KitCCompileOptions* opts, - KitSlice name, const KitSlice* in, - KitWriter* w) { + const KitPreprocessOptions* pp, KitSlice name, + const KitSlice* in, KitWriter* w) { KitObjBuilder* ob = NULL; - KitStatus st = compile_c_obj(c, opts, name, in, &ob); + KitStatus st = compile_c_obj(c, opts, pp, name, in, &ob); if (st == KIT_OK && !opts->code.emit_c_source) st = kit_obj_builder_emit(ob, w); kit_obj_builder_free(ob); @@ -390,6 +390,7 @@ static int mode_emit_impl(const char* src_path, const char* out_path, KitCompiler* c = NULL; KitSlice in; KitCCompileOptions opts; + KitPreprocessOptions pp; KitWriter* w = NULL; int rc = 0; size_t len = 0; @@ -414,14 +415,16 @@ static int mode_emit_impl(const char* src_path, const char* out_path, /* --emit-c forces opt_level=0 inside CG anyway, but be explicit so the * env-driven opt level doesn't surprise. */ opts.code.opt_level = emit_c ? 0 : opt_level_from_env(); - add_test_system_includes(&opts); + memset(&pp, 0, sizeof pp); + add_test_system_includes(&pp); (void)kit_writer_mem(&g_heap, &w); if (emit_c) { opts.code.emit_c_source = true; opts.code.c_source_writer = w; } - if (compile_c_emit(c, &opts, kit_slice_cstr(src_path), &in, w) != KIT_OK) { + if (compile_c_emit(c, &opts, &pp, kit_slice_cstr(src_path), &in, w) != + KIT_OK) { kit_writer_close(w); kit_compiler_free(c); free(src); @@ -480,6 +483,7 @@ static int mode_jit(const char* src_path) { KitCompiler* c = NULL; KitSlice in; KitCCompileOptions opts; + KitPreprocessOptions pp; KitObjBuilder* ob = NULL; KitLinkArchiveInput rt_archive; KitLinkSessionOptions lopts; @@ -505,9 +509,11 @@ static int mode_jit(const char* src_path) { in.len = src_len; memset(&opts, 0, sizeof opts); opts.code.opt_level = opt_level_from_env(); - add_test_system_includes(&opts); + memset(&pp, 0, sizeof pp); + add_test_system_includes(&pp); - if (compile_c_obj(c, &opts, kit_slice_cstr(src_path), &in, &ob) != KIT_OK || + if (compile_c_obj(c, &opts, &pp, kit_slice_cstr(src_path), &in, &ob) != + KIT_OK || !ob) { kit_compiler_free(c); free(src); diff --git a/test/test.mk b/test/test.mk @@ -63,6 +63,7 @@ TEST_TARGETS = \ test-driver-ar \ test-driver-cas \ test-driver-cc \ + test-driver-compile \ test-driver-objcopy \ test-driver-objdump \ test-driver-pkg \ @@ -185,11 +186,14 @@ test-images: test-cf-corpus-selftest: @bash test/lib/kit_corpus_selftest.sh -test-driver: test-driver-cc test-driver-ar test-driver-cas test-driver-strip test-driver-objcopy test-driver-objdump test-driver-pkg test-driver-strings test-driver-tools +test-driver: test-driver-cc test-driver-compile test-driver-ar test-driver-cas test-driver-strip test-driver-objcopy test-driver-objdump test-driver-pkg test-driver-strings test-driver-tools test-driver-cc: bin @KIT=$(abspath $(BIN)) sh test/driver/run.sh +test-driver-compile: bin + @KIT=$(abspath $(BIN)) sh test/compile/run.sh + # test-cbackend: --emit=c C-source backend, driven through three # frontends — parse-runner (C), toy-runner (toy), wasm-runner (wat/wasm). # Each invokes its existing runner with paths=C so a single corpus per