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:
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