commit 58a41780a77ed786a88361881b40d8059cc29a94
parent 438e6b094ea31ed5ec9787d451e59ddc9a7770f9
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 4 Jun 2026 08:18:05 -0700
driver: add build-exe/build-lib/build-obj, retire compile
Kit-native build verbs that compile a polyglot source set (C/asm/toy/wasm,
resolved per file) entirely in memory and produce the final artifact in one
invocation:
build-exe link an executable
build-lib static .a (dynamic/shared not yet supported)
build-obj one object, --emit=asm|c|ir, -fsyntax-only, or an `ld -r` combine
of N sources; full replacement for the retired `compile` tool
Flags split into global/per-output and a scopable set (-I/-isystem/-D/-U, -x,
-X<lang>) overridable per source group via `--group [flags] -- sources`.
Per-language frontend flags route through -X<lang>.
Factoring:
- driver/lib/link_engine: build-session/add-in-order/emit step; cc's link
path now routes through it (no behavior change).
- driver/lib/archive_engine: in-memory object serialize + ar symbol index.
build-obj's multi-source combine matches `ld -r` symbol-for-symbol.
Register the tools (config gate, main table, default install group), remove
compile.c + its test, migrate test/compile -> test/buildcmds, update
doc/DRIVER.md, and add doc/plan/TODO.md cataloguing pre-existing issues found
along the way.
Diffstat:
17 files changed, 2917 insertions(+), 738 deletions(-)
diff --git a/doc/DRIVER.md b/doc/DRIVER.md
@@ -85,7 +85,9 @@ 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=`). |
+| `build-exe` | Kit-native build verb: compile a polyglot source set (C / asm / toy / wasm, per file) in memory and link it — with any `.o`/`.a`/`.so` inputs — into an executable. No intermediate files. |
+| `build-lib` | Compile a polyglot source set in memory into a static `.a` (default) or, with `-dynamic`, a shared library. |
+| `build-obj` | Compile sources to one object (or `--emit=asm\|c\|ir`, or `-fsyntax-only` check); multiple sources combine into one relocatable object (`ld -r`). The kit-native replacement for the retired `compile` tool. |
| `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. |
@@ -112,16 +114,20 @@ 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`
+`cc` vs `build-*`: `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`.
+non-C sources by suffix. The `build-*` trio is the kit-native front door to the
+same in-memory, no-temp-files compile+link pipeline, without pretending to be
+`gcc`: every command is polyglot (language resolved per file), forwards
+per-language frontend flags via `-X<lang> FLAG`, and scopes compile flags to
+individual sources via `--group [scopable flags] -- sources…`. Flags split into
+two tiers — global / per-output flags (target, `-O`/`-g`, all link and output
+flags) that apply to the whole build, and a small scopable set (`-I`/`-isystem`/
+`-D`/`-U`, `-x`, `-X<lang>`) that forms a baseline and may be overridden inside a
+`--group`. `cc` and `build-*` share one link path (`driver/lib/link_engine.c`)
+and the same language-neutral per-source compile step
+(`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*
@@ -152,13 +158,22 @@ and consistent. All are freestanding (no host calls except through `env/`).
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
+ one source" step shared by `cc` and the `build-*` verbs. 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.
+- **link_engine** (`lib/link_engine.c`): the reusable "build a `KitLinkSession`,
+ add inputs in command-line order, and emit" step shared by `cc` and
+ `build-exe`/`build-lib -dynamic`. Inputs are already loaded/compiled (in-memory
+ builders, byte objects/archives/DSOs) and an ordered `KitLinkInputOrder` list
+ drives the add sequence; the caller owns hosted/runtime wiring and the writer.
+- **archive_engine** (`lib/archive_engine.c`): `driver_archive_emit` serializes
+ each compiled object builder to bytes (via an in-memory writer), collects the
+ per-member symbol index, and writes a POSIX `ar` static archive — the
+ `build-lib` static path.
- **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/doc/plan/README.md b/doc/plan/README.md
@@ -18,3 +18,4 @@ shrinks to whatever remains open.
| [IMAGE_INSPECT.md](IMAGE_INSPECT.md) | Extending object inspection to executables and shared libraries. | [../OBJ.md](../OBJ.md) |
| [BUILD.md](BUILD.md) | A new content-addressed build coordinator (Bazel/Nix-style incremental builds layered on the CAS) — storage state machine, caching algorithm, recipe protocol. Distinct from `../BUILD.md` (kit's own Makefile build). | — (new subsystem) |
| [BUILD_COMMANDS.md](BUILD_COMMANDS.md) | The kit-native `build-exe`/`build-lib`/`build-obj` verbs that replace `compile`: polyglot, in-memory compile+link with `--group` flag scoping and full link-flag control. Distinct from `BUILD.md` (the CAS coordinator). | [../DRIVER.md](../DRIVER.md) |
+| [TODO.md](TODO.md) | A catalog of known bugs, code smells, and test-infra failures to address — driver memory leaks, a `-Wl` rpath overflow, a test-infra prerequisite gap, and build-commands follow-ups. Not a roadmap; a backlog to drain. | — |
diff --git a/doc/plan/TODO.md b/doc/plan/TODO.md
@@ -0,0 +1,80 @@
+# kit — deferred fixes & code-smell backlog
+
+A catalog of known bugs, code smells, and test-infra failures to address. Each
+entry names the symptom, the location, why it matters, and a suggested fix.
+
+## Memory / correctness (driver)
+
+### 1. `cc` leaks one `KitObjBuilder` per source on the link path
+- **Where:** `driver/cmd/cc.c` `cc_run_link_exe` (the `out:` cleanup frees only
+ the `objs` array, never `kit_obj_builder_free(objs[i])`).
+- **Why:** A link session only *borrows* builders added via
+ `kit_link_session_add_obj` — `src/link/link.c:39,449` ("borrowed; caller still
+ owns"), and `kit_link_session_free` frees only its own `publish_objs` pointer
+ array, not the builders. The compiler does not free them either (the
+ `cc_run_check` path frees them explicitly, which establishes that ownership
+ stays with the caller). Every `kit cc a.c b.c -o exe` leaks one builder
+ (sections/symbols/relocs/atoms buffers) per source. The comment above
+ `cc_run_link_exe` ("The compiler owns the per-source KitObjBuilders for the
+ lifetime of the link") is wrong and masks this.
+- **Fix:** Free `objs[i]` after the link completes (the session is already freed
+ by then), and correct the comment.
+
+### 2. `cc` leaks `rpath_slices` on every `-rpath` link
+- **Where:** `driver/cmd/cc.c` `cc_run_link_exe` (`rpath_slices` is
+ `driver_alloc_zeroed`'d, with no matching `driver_free` anywhere in the
+ function).
+- **Why:** Any `kit cc … -Wl,-rpath=DIR` / `-rpath` leaks
+ `nrpaths * sizeof(KitSlice)`.
+- **Fix:** Free `rpath_slices` in `cc_run_link_exe`'s cleanup.
+
+### 3. Fixed-bound `rpaths` array can overflow on a comma-packed `-Wl,` token
+- **Where:** `driver/cmd/cc.c` `cc_record_wl` and `driver/cmd/build.c`
+ `build_record_wl`; in both, `o->rpaths` is sized `argc + 16`.
+- **Why:** A single argv element `-Wl,-rpath=a,-rpath=b,…` counts as one toward
+ `argc` but is split on commas, pushing one entry per `-rpath=` via
+ `o->rpaths[o->nrpaths++]`. With more than `argc + 16` rpath entries in one
+ token, the write runs past the heap allocation (OOB write). Exotic — real
+ builds use separate `-Wl,-rpath=` tokens — but a genuine overflow.
+- **Fix:** Bounds-check `nrpaths` against the allocation, size `rpaths` from a
+ worst-case comma count, or grow it dynamically.
+
+## Test infrastructure
+
+### 4. `make test-rt-runtime` fails standalone (missing helper prerequisite)
+- **Symptom:** `make test-rt-runtime` aborts with
+ `link-exe-runner missing at build/test/link-exe-runner` (Error 2). The target
+ references `build/test/link-exe-runner` but does not build it as a
+ prerequisite, so a clean standalone invocation cannot succeed.
+- **Fix:** Declare the `link-exe-runner` build as a prerequisite of
+ `test-rt-runtime` (or document the prior target that must run first).
+
+### 5. Driver leaks are invisible on macOS
+- **Why:** The driver env heap is libc-backed (no arena bulk-free at
+ `driver_env_fini`), and `ASAN_OPTIONS` (`mk/flags.mk`) sets
+ `halt_on_error`/`abort_on_error` but not `detect_leaks`. macOS ASan ships no
+ LeakSanitizer, so process-lifetime leaks (#1, #2) never trip locally — they
+ surface only under Linux ASan+LSan.
+- **Fix:** Add a Linux LSan lane (CI or periodic) so driver leaks are caught,
+ then keep it green by scrubbing #1/#2.
+
+## Code smells / factoring
+
+### 6. `cc` and `build` duplicate ~400 lines of link orchestration
+- **Where:** `driver/cmd/build.c` re-implements `cc.c`'s hosted-profile
+ application, runtime-archive insertion, `-Wl,` parsing, `--build-id` parsing,
+ PE-subsystem matching, and Windows lib-dir handling.
+- **Why it matters:** A fix to `-Wl` parsing or hosted wiring (e.g. #2, #3) has
+ to land in two places. The hosted/runtime/link-flag orchestration should be
+ factored into a shared `driver/lib` unit the way `link_engine` already shares
+ the session step.
+
+## build-commands follow-ups
+
+- **`build-exe -shared` is a silent no-op.** `-dynamic` is the exe default
+ (fine), but `-shared` (gcc spelling for "make a shared library") silently
+ builds an executable. Emit a diagnostic instead.
+- **Diagnostic: `-x`/`-X` at the end of a `--group` flag section eats the `--`.**
+ `kit build-obj --group -x -- a.c` reports `unsupported -x language: --` instead
+ of a clearer "missing argument / `--` before sources". Safe (no state
+ corruption), just a confusing message.
diff --git a/driver/cmd/build.c b/driver/cmd/build.c
@@ -0,0 +1,2333 @@
+#include <kit/compile.h>
+#include <kit/core.h>
+#include <kit/link.h>
+#include <kit/preprocess.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "archive_engine.h"
+#include "cflags.h"
+#include "compile_engine.h"
+#include "driver.h"
+#include "hosted.h"
+#include "lib_resolve.h"
+#include "link_engine.h"
+#include "runtime.h"
+
+/* `kit build-exe` / `build-lib` / `build-obj` — the kit-native build verbs.
+ *
+ * Each command is polyglot (C / asm / toy / wasm resolved per file), compiles
+ * entirely in memory, and writes no intermediate files. They share one
+ * parse+run parameterized by output kind:
+ *
+ * build-exe link an executable (link session, OUTPUT_EXE)
+ * build-lib static .a (archive of compiled objects;
+ * dynamic/shared not yet supported)
+ * build-obj one object; or a relocatable (compile each source / link
+ * combine of N sources; or session, OUTPUT_RELOCATABLE)
+ * --emit=asm|c|ir; -fsyntax-only
+ *
+ * build-obj fully replaces the retired `compile` tool. Two flag tiers:
+ * - global / per-output flags apply to the whole build (target, -O/-g, link
+ * flags, output flags); they may appear anywhere outside a --group.
+ * - scopable flags (-I/-isystem/-D/-U, -x, -X<lang>) form a global baseline
+ * and may be overridden inside a `--group [flags] -- sources...` block.
+ *
+ * Per-language frontend flags route through `-X<lang> FLAG` (e.g.
+ * `-Xwasm -mfeature=simd128`). */
+
+/* Stand-in for "no -x; resolve language from the path suffix at compile time." */
+#define BUILD_LANG_AUTO ((KitLanguage)KIT_LANG_COUNT)
+
+typedef enum BuildOutputKind {
+ BUILD_OUT_EXE,
+ BUILD_OUT_LIB,
+ BUILD_OUT_OBJ,
+} BuildOutputKind;
+
+typedef enum BuildEmit {
+ BUILD_EMIT_OBJ = 0,
+ BUILD_EMIT_ASM,
+ BUILD_EMIT_C,
+ BUILD_EMIT_IR,
+} BuildEmit;
+
+typedef enum BuildLinkKind {
+ BUILD_LINK_SOURCE,
+ BUILD_LINK_OBJECT,
+ BUILD_LINK_ARCHIVE,
+ BUILD_LINK_DSO,
+ BUILD_LINK_LIB,
+} BuildLinkKind;
+
+typedef struct BuildLinkItem {
+ uint8_t kind; /* BuildLinkKind */
+ uint8_t pad[3];
+ uint32_t index;
+} BuildLinkItem;
+
+typedef struct BuildSource {
+ const char* path; /* argv-borrowed */
+ uint32_t group; /* index into o->groups[]; 0 = global/bare baseline */
+} BuildSource;
+
+typedef struct BuildArchiveInput {
+ const char* path;
+ int owned;
+ size_t owned_size;
+ uint8_t whole_archive;
+ uint8_t link_mode;
+ uint8_t group_id;
+ uint8_t pad;
+} BuildArchiveInput;
+
+typedef struct BuildDsoInput {
+ const char* path;
+ int owned;
+ size_t owned_size;
+} BuildDsoInput;
+
+typedef struct BuildPendingLib {
+ const char* name;
+ uint8_t whole_archive;
+ uint8_t link_mode;
+ uint8_t group_id;
+ uint8_t resolved_kind; /* BuildLinkKind: ARCHIVE or DSO */
+ uint32_t resolved_index;
+} BuildPendingLib;
+
+/* One -X<lang> frontend flag token, scoped to a language. */
+typedef struct BuildFeFlag {
+ uint8_t lang; /* KitLanguage */
+ uint8_t pad[3];
+ char* tok; /* argv-borrowed */
+} BuildFeFlag;
+
+/* A compile-flag scope: the global baseline (groups[0]) plus one entry per
+ * `--group`. Each carries its own preprocessor delta, language override, and
+ * per-language frontend flags. */
+typedef struct BuildGroup {
+ DriverCflags cf;
+ int cf_inited;
+ KitLanguage forced_lang; /* BUILD_LANG_AUTO if -x not set */
+ BuildFeFlag* fe;
+ uint32_t nfe;
+ /* Merged preprocessor view (group delta over the global baseline), filled
+ * after parsing. For groups[0] this borrows cf directly; for real groups it
+ * points at the m_* arrays below. */
+ KitPreprocessOptions pp;
+ const char** m_inc;
+ uint32_t m_ninc;
+ const char** m_sys;
+ uint32_t m_nsys;
+ KitDefine* m_def;
+ uint32_t m_ndef;
+ uint32_t m_def_cap; /* allocation size; m_ndef <= cap once globals are shadowed */
+ KitSlice* m_und;
+ uint32_t m_nund;
+} BuildGroup;
+
+typedef struct BuildOptions {
+ DriverEnv* env;
+ const char* tool; /* "build-exe" | "build-lib" | "build-obj" */
+ int kind; /* BuildOutputKind */
+ const char* driver_path; /* argv[0] */
+ size_t argv_bound;
+
+ /* Output / per-output state. */
+ int emit; /* BuildEmit (build-obj) */
+ int syntax_only;
+ int opt_level;
+ int debug_info;
+ int dynamic; /* -dynamic / -shared */
+ int shared; /* computed: kind==lib && dynamic */
+ int static_link; /* -static */
+ int pie; /* -pie */
+ int gc_sections; /* -Wl,--gc-sections */
+ int strip_debug; /* -Wl,-S / -Wl,--strip-debug */
+ int function_sections; /* -ffunction-sections */
+ int data_sections; /* -fdata-sections */
+ uint8_t default_visibility; /* KitSymVis */
+ int warnings_are_errors;
+ uint32_t max_errors;
+ const char* output_path;
+
+ KitTargetSpec target;
+ DriverTargetFeatures target_features;
+
+ /* Link knobs (build-exe / shared build-lib). */
+ const char* entry;
+ const char* linker_script;
+ uint16_t pe_subsystem;
+ const char* soname;
+ const char** rpaths;
+ uint32_t nrpaths;
+ int new_dtags;
+ const char* interp_path;
+ uint8_t build_id_mode;
+ uint8_t* build_id_bytes;
+ uint32_t build_id_len;
+ uint64_t epoch;
+
+ /* Hosted libc / sysroot state. */
+ const char* sysroot;
+ const char* support_dir;
+ int freestanding;
+ int nostdinc;
+ int no_stdlib;
+ int no_defaultlibs;
+ int no_startfiles;
+ int wants_hosted_libc;
+ DriverHostedPlan hosted;
+ char* owned_sysroot_lib_dir;
+ size_t owned_sysroot_lib_dir_size;
+
+ /* Sources + compile-flag scopes. */
+ BuildSource* sources;
+ uint32_t nsources;
+ BuildGroup* groups;
+ uint32_t ngroups;
+ uint32_t cur_group; /* scope for newly seen sources */
+
+ /* Link inputs (build-exe). */
+ const char** object_files;
+ uint32_t nobject_files;
+ BuildArchiveInput* archives;
+ uint32_t narchives;
+ BuildDsoInput* dsos;
+ uint32_t ndsos;
+ const char** lib_search_paths;
+ uint32_t nlib_search_paths;
+ BuildPendingLib* pending_libs;
+ uint32_t npending_libs;
+ BuildLinkItem* link_items;
+ uint32_t nlink_items;
+ uint8_t cur_whole_archive;
+ uint8_t cur_link_mode;
+ uint8_t cur_group_id;
+} BuildOptions;
+
+/* ===================================================================== */
+/* small parse helpers */
+/* ===================================================================== */
+
+static int build_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 char* build_dup_span(DriverEnv* env, const char* s, size_t n) {
+ char* buf = driver_alloc(env, n + 1u);
+ if (!buf) return NULL;
+ driver_memcpy(buf, s, n);
+ buf[n] = '\0';
+ return buf;
+}
+
+static int build_parse_hex_bytes(DriverEnv* env, const char* s,
+ uint8_t** out_bytes, uint32_t* out_len) {
+ size_t n = driver_strlen(s);
+ uint8_t* bs;
+ size_t i;
+ if (n == 0 || (n & 1u)) return 1;
+ bs = driver_alloc(env, n / 2u);
+ if (!bs) return 1;
+ for (i = 0; i < n; i += 2) {
+ unsigned hi, lo;
+ char c1 = s[i], c2 = s[i + 1];
+ if (c1 >= '0' && c1 <= '9')
+ hi = (unsigned)(c1 - '0');
+ else if (c1 >= 'a' && c1 <= 'f')
+ hi = (unsigned)(c1 - 'a' + 10);
+ else if (c1 >= 'A' && c1 <= 'F')
+ hi = (unsigned)(c1 - 'A' + 10);
+ else {
+ driver_free(env, bs, n / 2u);
+ return 1;
+ }
+ if (c2 >= '0' && c2 <= '9')
+ lo = (unsigned)(c2 - '0');
+ else if (c2 >= 'a' && c2 <= 'f')
+ lo = (unsigned)(c2 - 'a' + 10);
+ else if (c2 >= 'A' && c2 <= 'F')
+ lo = (unsigned)(c2 - 'A' + 10);
+ else {
+ driver_free(env, bs, n / 2u);
+ return 1;
+ }
+ bs[i / 2u] = (uint8_t)((hi << 4) | lo);
+ }
+ *out_bytes = bs;
+ *out_len = (uint32_t)(n / 2u);
+ return 0;
+}
+
+static int build_record_build_id(BuildOptions* o, const char* val) {
+ if (driver_streq(val, "none")) {
+ o->build_id_mode = KIT_BUILDID_NONE;
+ return 0;
+ }
+ if (driver_streq(val, "sha256")) {
+ o->build_id_mode = KIT_BUILDID_SHA256;
+ return 0;
+ }
+ if (driver_streq(val, "uuid")) {
+ o->build_id_mode = KIT_BUILDID_UUID;
+ return 0;
+ }
+ if (driver_strneq(val, "0x", 2)) {
+ if (build_parse_hex_bytes(o->env, val + 2, &o->build_id_bytes,
+ &o->build_id_len) != 0) {
+ driver_errf(o->tool,
+ "--build-id=0x... requires an even-length hex string");
+ return 1;
+ }
+ o->build_id_mode = KIT_BUILDID_USER;
+ return 0;
+ }
+ driver_errf(o->tool, "unknown --build-id value: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(val)));
+ return 1;
+}
+
+static int build_record_mcmodel(BuildOptions* o, const char* val) {
+ if (driver_streq(val, "small") || driver_streq(val, "medlow")) {
+ o->target.code_model = KIT_CM_SMALL;
+ return 0;
+ }
+ if (driver_streq(val, "medium") || driver_streq(val, "medany")) {
+ o->target.code_model = KIT_CM_MEDIUM;
+ return 0;
+ }
+ if (driver_streq(val, "large")) {
+ o->target.code_model = KIT_CM_LARGE;
+ return 0;
+ }
+ driver_errf(o->tool, "unknown -mcmodel value: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(val)));
+ return 1;
+}
+
+static int build_subsystem_eq(const char* val, size_t n, const char* want) {
+ size_t i;
+ for (i = 0; want[i]; ++i) {
+ char a, b;
+ if (i >= n) return 0;
+ a = val[i];
+ b = want[i];
+ if (a >= 'a' && a <= 'z') a = (char)(a - 'a' + 'A');
+ if (b >= 'a' && b <= 'z') b = (char)(b - 'a' + 'A');
+ if (a != b) return 0;
+ }
+ return i == n || val[i] == ',';
+}
+
+static int build_record_pe_subsystem(BuildOptions* o, const char* val,
+ size_t n) {
+ if (build_subsystem_eq(val, n, "CONSOLE") || build_subsystem_eq(val, n, "CUI")) {
+ o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI;
+ return 0;
+ }
+ if (build_subsystem_eq(val, n, "WINDOWS") || build_subsystem_eq(val, n, "GUI")) {
+ o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI;
+ return 0;
+ }
+ driver_errf(o->tool, "unsupported subsystem: %.*s", (int)n, val);
+ return 1;
+}
+
+/* Parse one -Wl,X[,Y...] linker pass-through (subset shared with `cc`). */
+static int build_record_wl(BuildOptions* o, const char* arg) {
+ const char* p = arg;
+ int expect_rpath = 0, expect_soname = 0, expect_interp = 0, expect_sub = 0;
+ while (*p) {
+ const char* tok = p;
+ size_t n = 0;
+ while (p[n] && p[n] != ',') ++n;
+ p = tok + n + (tok[n] == ',' ? 1 : 0);
+
+ if (expect_rpath || expect_soname || expect_interp || expect_sub) {
+ char* buf = build_dup_span(o->env, tok, n);
+ if (!buf) {
+ driver_errf(o->tool, "out of memory");
+ return 1;
+ }
+ if (expect_rpath) o->rpaths[o->nrpaths++] = buf;
+ if (expect_soname) o->soname = buf;
+ if (expect_interp) o->interp_path = buf;
+ if (expect_sub) {
+ int rc = build_record_pe_subsystem(o, buf, driver_strlen(buf));
+ driver_free(o->env, buf, n + 1u);
+ if (rc != 0) return 1;
+ }
+ expect_rpath = expect_soname = expect_interp = expect_sub = 0;
+ continue;
+ }
+ if (n >= 8 && driver_strneq(tok, "-soname=", 8)) {
+ o->soname = build_dup_span(o->env, tok + 8, n - 8);
+ if (!o->soname) {
+ driver_errf(o->tool, "out of memory");
+ return 1;
+ }
+ continue;
+ }
+ if (n == 7 && driver_strneq(tok, "-soname", 7)) {
+ expect_soname = 1;
+ continue;
+ }
+ if (n >= 7 && driver_strneq(tok, "-rpath=", 7)) {
+ char* buf = build_dup_span(o->env, tok + 7, n - 7);
+ if (!buf) {
+ driver_errf(o->tool, "out of memory");
+ return 1;
+ }
+ o->rpaths[o->nrpaths++] = buf;
+ continue;
+ }
+ if (n == 6 && driver_strneq(tok, "-rpath", 6)) {
+ expect_rpath = 1;
+ continue;
+ }
+ if (n >= 16 && driver_strneq(tok, "-dynamic-linker=", 16)) {
+ o->interp_path = build_dup_span(o->env, tok + 16, n - 16);
+ if (!o->interp_path) {
+ driver_errf(o->tool, "out of memory");
+ return 1;
+ }
+ continue;
+ }
+ if (n == 15 && driver_strneq(tok, "-dynamic-linker", 15)) {
+ expect_interp = 1;
+ continue;
+ }
+ if (n == 18 && driver_strneq(tok, "--enable-new-dtags", 18)) {
+ o->new_dtags = 1;
+ continue;
+ }
+ if (n == 19 && driver_strneq(tok, "--disable-new-dtags", 19)) {
+ o->new_dtags = 0;
+ continue;
+ }
+ if (n == 13 && driver_strneq(tok, "--gc-sections", 13)) {
+ o->gc_sections = 1;
+ continue;
+ }
+ if (n == 16 && driver_strneq(tok, "--no-gc-sections", 16)) {
+ o->gc_sections = 0;
+ continue;
+ }
+ if ((n == 2 && driver_strneq(tok, "-S", 2)) ||
+ (n == 13 && driver_strneq(tok, "--strip-debug", 13))) {
+ o->strip_debug = 1;
+ continue;
+ }
+ if (n >= 11 && driver_strneq(tok, "--build-id=", 11)) {
+ char* buf = build_dup_span(o->env, tok + 11, n - 11);
+ int rc;
+ if (!buf) {
+ driver_errf(o->tool, "out of memory");
+ return 1;
+ }
+ rc = build_record_build_id(o, buf);
+ driver_free(o->env, buf, n - 11 + 1u);
+ if (rc != 0) return 1;
+ continue;
+ }
+ if (n == 10 && driver_strneq(tok, "--build-id", 10)) {
+ o->build_id_mode = KIT_BUILDID_SHA256;
+ continue;
+ }
+ if (n >= 12 && driver_strneq(tok, "--subsystem=", 12)) {
+ if (build_record_pe_subsystem(o, tok + 12, n - 12) != 0) return 1;
+ continue;
+ }
+ if (n == 11 && driver_strneq(tok, "--subsystem", 11)) {
+ expect_sub = 1;
+ continue;
+ }
+ if (n >= 11 && driver_strneq(tok, "/SUBSYSTEM:", 11)) {
+ if (build_record_pe_subsystem(o, tok + 11, n - 11) != 0) return 1;
+ continue;
+ }
+ driver_errf(o->tool, "unsupported -Wl, token: %.*s", (int)n, tok);
+ return 1;
+ }
+ if (expect_rpath || expect_soname || expect_interp || expect_sub) {
+ driver_errf(o->tool, "-Wl option requires another comma argument");
+ return 1;
+ }
+ return 0;
+}
+
+static int build_lang_from_name(const char* name, KitLanguage* out) {
+ if (driver_streq(name, "c")) {
+ *out = KIT_LANG_C;
+ } else if (driver_streq(name, "asm") || driver_streq(name, "s")) {
+ *out = KIT_LANG_ASM;
+ } else if (driver_streq(name, "toy")) {
+ *out = KIT_LANG_TOY;
+ } else if (driver_streq(name, "wasm") || driver_streq(name, "wat")) {
+ *out = KIT_LANG_WASM;
+ } else {
+ return 1;
+ }
+ return 0;
+}
+
+/* ===================================================================== */
+/* allocation / lifetime */
+/* ===================================================================== */
+
+static int build_group_cf_init(BuildOptions* o, BuildGroup* g) {
+ if (g->cf_inited) return 0;
+ if (driver_cflags_init(&g->cf, o->env,
+ (int)(o->argv_bound + DRIVER_HOSTED_MAX_DEFINES +
+ DRIVER_HOSTED_MAX_INCLUDES)) != 0)
+ return 1;
+ g->cf_inited = 1;
+ g->forced_lang = BUILD_LANG_AUTO;
+ g->fe = driver_alloc_zeroed(o->env, o->argv_bound * sizeof(*g->fe));
+ return g->fe ? 0 : 1;
+}
+
+static int build_alloc(BuildOptions* o, int argc) {
+ size_t bound = (size_t)argc + 16u;
+ o->argv_bound = bound;
+ o->sources = driver_alloc_zeroed(o->env, bound * sizeof(*o->sources));
+ o->groups = driver_alloc_zeroed(o->env, bound * sizeof(*o->groups));
+ o->object_files =
+ driver_alloc_zeroed(o->env, bound * sizeof(*o->object_files));
+ o->archives = driver_alloc_zeroed(o->env, bound * sizeof(*o->archives));
+ o->dsos = driver_alloc_zeroed(o->env, bound * sizeof(*o->dsos));
+ o->lib_search_paths =
+ driver_alloc_zeroed(o->env, bound * sizeof(*o->lib_search_paths));
+ o->pending_libs =
+ driver_alloc_zeroed(o->env, bound * sizeof(*o->pending_libs));
+ o->link_items = driver_alloc_zeroed(o->env, bound * sizeof(*o->link_items));
+ o->rpaths = driver_alloc_zeroed(o->env, bound * sizeof(*o->rpaths));
+ if (!o->sources || !o->groups || !o->object_files || !o->archives ||
+ !o->dsos || !o->lib_search_paths || !o->pending_libs || !o->link_items ||
+ !o->rpaths) {
+ driver_errf(o->tool, "out of memory");
+ return 1;
+ }
+ o->new_dtags = 1;
+ o->cur_link_mode = KIT_LM_DEFAULT;
+ /* groups[0] is the global / bare baseline. */
+ o->ngroups = 1;
+ if (build_group_cf_init(o, &o->groups[0]) != 0 ||
+ driver_target_features_init(&o->target_features, o->env, argc) != 0) {
+ driver_errf(o->tool, "out of memory");
+ return 1;
+ }
+ return 0;
+}
+
+static void build_release(BuildOptions* o) {
+ uint32_t i;
+ size_t bound = o->argv_bound;
+ for (i = 0; i < o->narchives; ++i)
+ if (o->archives[i].owned)
+ driver_free(o->env, (void*)o->archives[i].path, o->archives[i].owned_size);
+ for (i = 0; i < o->ndsos; ++i)
+ if (o->dsos[i].owned)
+ driver_free(o->env, (void*)o->dsos[i].path, o->dsos[i].owned_size);
+ for (i = 0; i < o->ngroups; ++i) {
+ BuildGroup* g = &o->groups[i];
+ if (g->cf_inited) driver_cflags_fini(&g->cf, o->env);
+ if (g->fe) driver_free(o->env, g->fe, bound * sizeof(*g->fe));
+ if (g->m_inc) driver_free(o->env, g->m_inc, g->m_ninc * sizeof(*g->m_inc));
+ if (g->m_sys) driver_free(o->env, g->m_sys, g->m_nsys * sizeof(*g->m_sys));
+ if (g->m_def) driver_free(o->env, g->m_def, g->m_def_cap * sizeof(*g->m_def));
+ if (g->m_und) driver_free(o->env, g->m_und, g->m_nund * sizeof(*g->m_und));
+ }
+ if (o->build_id_bytes)
+ driver_free(o->env, o->build_id_bytes, o->build_id_len);
+ if (o->owned_sysroot_lib_dir)
+ driver_free(o->env, o->owned_sysroot_lib_dir, o->owned_sysroot_lib_dir_size);
+ driver_hosted_plan_fini(o->env, &o->hosted);
+ driver_target_features_fini(&o->target_features, o->env);
+ if (o->sources) driver_free(o->env, o->sources, bound * sizeof(*o->sources));
+ if (o->groups) driver_free(o->env, o->groups, bound * sizeof(*o->groups));
+ if (o->object_files)
+ driver_free(o->env, o->object_files, bound * sizeof(*o->object_files));
+ if (o->archives) driver_free(o->env, o->archives, bound * sizeof(*o->archives));
+ if (o->dsos) driver_free(o->env, o->dsos, bound * sizeof(*o->dsos));
+ if (o->lib_search_paths)
+ driver_free(o->env, o->lib_search_paths,
+ bound * sizeof(*o->lib_search_paths));
+ if (o->pending_libs)
+ driver_free(o->env, o->pending_libs, bound * sizeof(*o->pending_libs));
+ if (o->link_items)
+ driver_free(o->env, o->link_items, bound * sizeof(*o->link_items));
+ if (o->rpaths) driver_free(o->env, o->rpaths, bound * sizeof(*o->rpaths));
+}
+
+/* ===================================================================== */
+/* link-item bookkeeping (build-exe) */
+/* ===================================================================== */
+
+static void build_push_link_item(BuildOptions* o, uint8_t kind, uint32_t index) {
+ BuildLinkItem* it = &o->link_items[o->nlink_items++];
+ it->kind = kind;
+ it->index = index;
+}
+
+static void build_insert_link_item(BuildOptions* o, uint32_t pos, uint8_t kind,
+ uint32_t index) {
+ uint32_t i;
+ if (pos > o->nlink_items) pos = o->nlink_items;
+ for (i = o->nlink_items; i > pos; --i) o->link_items[i] = o->link_items[i - 1u];
+ o->link_items[pos].kind = kind;
+ o->link_items[pos].index = index;
+ o->nlink_items++;
+}
+
+static int build_append_hosted_input(BuildOptions* o,
+ const DriverHostedInput* in,
+ uint32_t insert_pos, int insert) {
+ uint32_t index;
+ uint8_t kind;
+ switch ((DriverHostedInputKind)in->kind) {
+ case DRIVER_HOSTED_INPUT_OBJECT:
+ index = o->nobject_files++;
+ o->object_files[index] = in->path;
+ kind = BUILD_LINK_OBJECT;
+ break;
+ case DRIVER_HOSTED_INPUT_ARCHIVE: {
+ BuildArchiveInput* ar = &o->archives[o->narchives++];
+ ar->path = in->path;
+ ar->link_mode = KIT_LM_DEFAULT;
+ index = o->narchives - 1u;
+ kind = BUILD_LINK_ARCHIVE;
+ break;
+ }
+ case DRIVER_HOSTED_INPUT_DSO: {
+ BuildDsoInput* d = &o->dsos[o->ndsos++];
+ d->path = in->path;
+ index = o->ndsos - 1u;
+ kind = BUILD_LINK_DSO;
+ break;
+ }
+ default:
+ driver_errf(o->tool, "internal error: unknown hosted input kind");
+ return 1;
+ }
+ if (insert)
+ build_insert_link_item(o, insert_pos, kind, index);
+ else
+ build_push_link_item(o, kind, index);
+ return 0;
+}
+
+static void build_insert_runtime_archive(BuildOptions* o,
+ DriverRuntimeArchive* rt,
+ uint32_t insert_pos) {
+ BuildArchiveInput* ar = &o->archives[o->narchives++];
+ ar->path = rt->path;
+ ar->owned = 1;
+ ar->owned_size = rt->path_size;
+ ar->whole_archive = rt->whole_archive;
+ ar->link_mode = rt->link_mode;
+ ar->group_id = rt->group_id;
+ rt->path = NULL;
+ rt->path_size = 0;
+ build_insert_link_item(o, insert_pos, BUILD_LINK_ARCHIVE, o->narchives - 1u);
+}
+
+/* ===================================================================== */
+/* positional classification */
+/* ===================================================================== */
+
+static int build_is_source(const char* s) {
+ return driver_has_suffix(s, ".c") || driver_has_suffix(s, ".S") ||
+ driver_has_suffix(s, ".s") || driver_has_suffix(s, ".toy") ||
+ driver_has_suffix(s, ".wat") || driver_has_suffix(s, ".wasm");
+}
+
+static int build_is_dso(const char* s) {
+ return driver_has_suffix(s, ".so") || driver_has_suffix(s, ".dylib") ||
+ driver_has_suffix(s, ".tbd");
+}
+
+/* The language forced (via -x) for sources in group `gi`: the group's own -x,
+ * else the global baseline's, else -1 (resolve by suffix). */
+static int build_scope_forced_lang(const BuildOptions* o, uint32_t gi) {
+ if (gi != 0 && o->groups[gi].forced_lang != BUILD_LANG_AUTO)
+ return (int)o->groups[gi].forced_lang;
+ if (o->groups[0].forced_lang != BUILD_LANG_AUTO)
+ return (int)o->groups[0].forced_lang;
+ return -1;
+}
+
+static int build_classify_positional(BuildOptions* o, const char* a) {
+ if (driver_streq(a, "-")) {
+ driver_errf(o->tool, "stdin ('-') is not supported; pass a source file");
+ return 1;
+ }
+ /* Explicit link-input suffixes are always link inputs — never reinterpreted
+ * as sources, even when -x forces a language. */
+ if (driver_has_suffix(a, ".o") || driver_has_suffix(a, ".obj")) {
+ o->object_files[o->nobject_files++] = a;
+ build_push_link_item(o, BUILD_LINK_OBJECT, o->nobject_files - 1u);
+ return 0;
+ }
+ if (driver_has_suffix(a, ".a")) {
+ BuildArchiveInput* ar = &o->archives[o->narchives++];
+ ar->path = a;
+ ar->whole_archive = o->cur_whole_archive;
+ ar->link_mode = o->cur_link_mode;
+ ar->group_id = o->cur_group_id;
+ build_push_link_item(o, BUILD_LINK_ARCHIVE, o->narchives - 1u);
+ return 0;
+ }
+ if (build_is_dso(a)) {
+ BuildDsoInput* d = &o->dsos[o->ndsos++];
+ d->path = a;
+ build_push_link_item(o, BUILD_LINK_DSO, o->ndsos - 1u);
+ return 0;
+ }
+ /* Otherwise a source: a recognized source suffix, or any file at all when a
+ * language is forced in scope (so `-x toy mykernel` compiles an extensionless
+ * or odd-suffix file). */
+ if (build_is_source(a) || build_scope_forced_lang(o, o->cur_group) >= 0) {
+ BuildSource* s = &o->sources[o->nsources];
+ s->path = a;
+ s->group = o->cur_group;
+ build_push_link_item(o, BUILD_LINK_SOURCE, o->nsources);
+ o->nsources++;
+ return 0;
+ }
+ driver_errf(o->tool,
+ "input does not have a recognized suffix: %.*s (use -x LANG)",
+ KIT_SLICE_ARG(kit_slice_cstr(a)));
+ return 1;
+}
+
+/* ===================================================================== */
+/* scopable-flag parsing (shared by global context and --group blocks) */
+/* ===================================================================== */
+
+/* Try to consume a scopable flag (-I/-isystem/-D/-U, -x, -X<lang>) at argv[*i]
+ * into group `g`. Returns 1 consumed, 0 not a scopable flag, -1 on error. */
+static int build_try_scopable(BuildOptions* o, BuildGroup* g, int argc,
+ char** argv, int* i) {
+ const char* a = argv[*i];
+ int r = driver_cflags_try_consume(&g->cf, o->env, o->tool, argc, argv, i);
+ if (r != 0) return r;
+
+ if (driver_streq(a, "-x")) {
+ KitLanguage lang;
+ if (++(*i) >= argc) {
+ driver_errf(o->tool, "-x requires an argument");
+ return -1;
+ }
+ if (build_lang_from_name(argv[*i], &lang) != 0) {
+ driver_errf(o->tool, "unsupported -x language: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(argv[*i])));
+ return -1;
+ }
+ g->forced_lang = lang;
+ return 1;
+ }
+ if (driver_strneq(a, "-X", 2) && a[2] != '\0') {
+ KitLanguage lang;
+ BuildFeFlag* f;
+ if (build_lang_from_name(a + 2, &lang) != 0) {
+ driver_errf(o->tool, "unsupported -X language: %.*s (use c|asm|toy|wasm)",
+ KIT_SLICE_ARG(kit_slice_cstr(a + 2)));
+ return -1;
+ }
+ if (++(*i) >= argc) {
+ driver_errf(o->tool, "%.*s requires a following flag",
+ KIT_SLICE_ARG(kit_slice_cstr(a)));
+ return -1;
+ }
+ f = &g->fe[g->nfe++];
+ f->lang = (uint8_t)lang;
+ f->tok = argv[*i];
+ return 1;
+ }
+ return 0;
+}
+
+/* Recognize the global / per-output flags, so a --group block can flag them as
+ * misplaced with a pointed diagnostic. */
+static int build_is_global_flag(const char* a) {
+ return driver_streq(a, "-o") || driver_strneq(a, "--output", 8) ||
+ driver_strneq(a, "-O", 2) || driver_streq(a, "-g") ||
+ driver_streq(a, "-S") || driver_strneq(a, "--emit=", 7) ||
+ driver_streq(a, "-fsyntax-only") || driver_strneq(a, "-fPIC", 5) ||
+ driver_strneq(a, "-fpic", 5) || driver_strneq(a, "-fPIE", 5) ||
+ driver_strneq(a, "-fpie", 5) || driver_strneq(a, "-fvisibility=", 13) ||
+ driver_streq(a, "-ffunction-sections") ||
+ driver_streq(a, "-fdata-sections") || driver_streq(a, "-static") ||
+ driver_streq(a, "-dynamic") || driver_streq(a, "-shared") ||
+ driver_streq(a, "-pie") || driver_streq(a, "-no-pie") ||
+ driver_streq(a, "-target") || driver_strneq(a, "--target", 8) ||
+ driver_strneq(a, "-l", 2) || driver_strneq(a, "-L", 2) ||
+ driver_streq(a, "-e") || driver_streq(a, "-T") ||
+ driver_strneq(a, "-Wl,", 4) || driver_strneq(a, "--build-id", 10) ||
+ driver_streq(a, "-Werror") || driver_strneq(a, "-fmax-errors=", 13) ||
+ driver_strneq(a, "-m", 2);
+}
+
+/* ===================================================================== */
+/* main parser */
+/* ===================================================================== */
+
+static int build_parse_group(BuildOptions* o, int argc, char** argv, int* i) {
+ BuildGroup* g;
+ uint32_t gid = o->ngroups++;
+ g = &o->groups[gid];
+ if (build_group_cf_init(o, g) != 0) {
+ driver_errf(o->tool, "out of memory");
+ return 1;
+ }
+ ++(*i); /* past --group */
+ /* scopable flag section, terminated by `--` */
+ for (; *i < argc && !driver_streq(argv[*i], "--"); ++(*i)) {
+ int r = build_try_scopable(o, g, argc, argv, i);
+ if (r < 0) return 1;
+ if (r > 0) continue;
+ if (build_is_global_flag(argv[*i])) {
+ driver_errf(o->tool,
+ "%.*s is a per-output flag; place it before any --group",
+ KIT_SLICE_ARG(kit_slice_cstr(argv[*i])));
+ return 1;
+ }
+ driver_errf(o->tool, "unsupported flag in --group: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(argv[*i])));
+ return 1;
+ }
+ if (*i >= argc) {
+ driver_errf(o->tool, "--group requires `--` before its sources");
+ return 1;
+ }
+ ++(*i); /* past `--` */
+ o->cur_group = gid; /* subsequent sources belong to this group */
+ return 0;
+}
+
+static int build_parse(int argc, char** argv, BuildOptions* o) {
+ int i;
+ o->target = driver_host_target();
+
+ for (i = 1; i < argc; ++i) {
+ const char* a = argv[i];
+
+ if (driver_streq(a, "--group")) {
+ if (build_parse_group(o, argc, argv, &i) != 0) return 1;
+ --i; /* parse_group leaves i at the next token; loop's ++i re-reads it */
+ continue;
+ }
+
+ /* Scopable flags in the global context feed the baseline (groups[0]). */
+ {
+ int r = build_try_scopable(o, &o->groups[0], argc, argv, &i);
+ if (r < 0) return 1;
+ if (r > 0) continue;
+ }
+
+ /* Output form. */
+ if (driver_streq(a, "-c")) {
+ o->emit = BUILD_EMIT_OBJ;
+ continue;
+ }
+ if (driver_streq(a, "-S") || driver_streq(a, "--emit=asm")) {
+ o->emit = BUILD_EMIT_ASM;
+ continue;
+ }
+ if (driver_streq(a, "--emit=obj")) {
+ o->emit = BUILD_EMIT_OBJ;
+ continue;
+ }
+ if (driver_streq(a, "--emit=c")) {
+ o->emit = BUILD_EMIT_C;
+ continue;
+ }
+ if (driver_streq(a, "--emit=ir")) {
+ o->emit = BUILD_EMIT_IR;
+ continue;
+ }
+ if (driver_streq(a, "-fsyntax-only")) {
+ o->syntax_only = 1;
+ continue;
+ }
+ if (driver_streq(a, "-o")) {
+ if (++i >= argc) {
+ driver_errf(o->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_strneq(a, "--output=", 9)) {
+ o->output_path = a + 9;
+ continue;
+ }
+ if (driver_streq(a, "--output")) {
+ if (++i >= argc) {
+ driver_errf(o->tool, "--output requires an argument");
+ return 1;
+ }
+ o->output_path = argv[i];
+ continue;
+ }
+
+ /* Optimization / debug / codegen knobs. */
+ 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") || driver_streq(a, "-O") ||
+ driver_streq(a, "-O3") || driver_streq(a, "-Os") ||
+ driver_streq(a, "-Oz") || driver_streq(a, "-Ofast")) {
+ o->opt_level = 2;
+ continue;
+ }
+ if (driver_streq(a, "-Werror") || driver_strneq(a, "-Werror=", 8)) {
+ o->warnings_are_errors = 1;
+ continue;
+ }
+ if (driver_strneq(a, "-fmax-errors=", 13)) {
+ uint64_t v;
+ if (build_parse_u64(a + 13, &v) != 0 || v > 0xFFFFFFFFu) {
+ driver_errf(o->tool, "-fmax-errors= requires a non-negative integer");
+ return 1;
+ }
+ o->max_errors = (uint32_t)v;
+ continue;
+ }
+ if (driver_streq(a, "-fPIC") || driver_streq(a, "-fpic")) {
+ o->target.pic = KIT_PIC_PIC;
+ continue;
+ }
+ if (driver_streq(a, "-fPIE") || driver_streq(a, "-fpie")) {
+ o->target.pic = KIT_PIC_PIE;
+ continue;
+ }
+ if (driver_streq(a, "-fno-PIC") || driver_streq(a, "-fno-pic") ||
+ driver_streq(a, "-fno-PIE") || driver_streq(a, "-fno-pie")) {
+ o->target.pic = KIT_PIC_NONE;
+ continue;
+ }
+ if (driver_streq(a, "-fvisibility=hidden")) {
+ o->default_visibility = KIT_SV_HIDDEN;
+ continue;
+ }
+ if (driver_streq(a, "-fvisibility=default")) {
+ o->default_visibility = KIT_SV_DEFAULT;
+ continue;
+ }
+ if (driver_strneq(a, "-fvisibility=", 13)) {
+ driver_errf(o->tool, "unsupported visibility: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(a + 13)));
+ return 1;
+ }
+ if (driver_streq(a, "-ffunction-sections")) {
+ o->function_sections = 1;
+ continue;
+ }
+ if (driver_streq(a, "-fno-function-sections")) {
+ o->function_sections = 0;
+ continue;
+ }
+ if (driver_streq(a, "-fdata-sections")) {
+ o->data_sections = 1;
+ continue;
+ }
+ if (driver_streq(a, "-fno-data-sections")) {
+ o->data_sections = 0;
+ continue;
+ }
+ if (driver_streq(a, "-ffreestanding")) {
+ o->freestanding = 1;
+ continue;
+ }
+ if (driver_streq(a, "-fhosted")) {
+ o->freestanding = 0;
+ continue;
+ }
+ if (driver_streq(a, "-nostdinc")) {
+ o->nostdinc = 1;
+ continue;
+ }
+ if (driver_streq(a, "-nostdlib")) {
+ o->no_stdlib = 1;
+ continue;
+ }
+ if (driver_streq(a, "-nodefaultlibs")) {
+ o->no_defaultlibs = 1;
+ continue;
+ }
+ if (driver_streq(a, "-nostartfiles")) {
+ o->no_startfiles = 1;
+ continue;
+ }
+
+ /* Link kind. */
+ if (driver_streq(a, "-static")) {
+ o->static_link = 1;
+ o->target.pic = KIT_PIC_NONE;
+ o->cur_link_mode = KIT_LM_STATIC;
+ continue;
+ }
+ if (driver_streq(a, "-dynamic")) {
+ o->dynamic = 1;
+ continue;
+ }
+ if (driver_streq(a, "-shared")) {
+ /* Hidden alias for -dynamic on build-lib; rejected elsewhere. */
+ o->dynamic = 1;
+ continue;
+ }
+ if (driver_streq(a, "-pie")) {
+ o->target.pic = KIT_PIC_PIE;
+ o->pie = 1;
+ continue;
+ }
+ if (driver_streq(a, "-no-pie")) {
+ o->target.pic = KIT_PIC_NONE;
+ continue;
+ }
+
+ /* Link inputs / flags. */
+ if (driver_strneq(a, "-L", 2)) {
+ const char* dir = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL);
+ if (!dir) {
+ driver_errf(o->tool, "-L requires an argument");
+ return 1;
+ }
+ o->lib_search_paths[o->nlib_search_paths++] = dir;
+ continue;
+ }
+ if (driver_strneq(a, "-l", 2)) {
+ const char* name = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL);
+ if (!name) {
+ driver_errf(o->tool, "-l requires an argument");
+ return 1;
+ }
+ if (driver_streq(name, "c") && !o->no_stdlib && !o->no_defaultlibs) {
+ o->wants_hosted_libc = 1;
+ continue;
+ }
+ {
+ BuildPendingLib* pl = &o->pending_libs[o->npending_libs++];
+ pl->name = name;
+ pl->whole_archive = o->cur_whole_archive;
+ pl->link_mode = o->cur_link_mode;
+ pl->group_id = o->cur_group_id;
+ build_push_link_item(o, BUILD_LINK_LIB, o->npending_libs - 1u);
+ }
+ continue;
+ }
+ if (driver_streq(a, "-e")) {
+ if (++i >= argc) {
+ driver_errf(o->tool, "-e requires an argument");
+ return 1;
+ }
+ o->entry = argv[i];
+ continue;
+ }
+ if (driver_streq(a, "-T")) {
+ if (++i >= argc) {
+ driver_errf(o->tool, "-T requires an argument");
+ return 1;
+ }
+ o->linker_script = argv[i];
+ continue;
+ }
+ if (driver_strneq(a, "-Wl,", 4)) {
+ if (build_record_wl(o, a + 4) != 0) return 1;
+ continue;
+ }
+ if (driver_streq(a, "-Xlinker")) {
+ if (++i >= argc) {
+ driver_errf(o->tool, "-Xlinker requires an argument");
+ return 1;
+ }
+ if (build_record_wl(o, argv[i]) != 0) return 1;
+ continue;
+ }
+ if (driver_strneq(a, "--build-id=", 11)) {
+ if (build_record_build_id(o, a + 11) != 0) return 1;
+ continue;
+ }
+ if (driver_streq(a, "-mwindows")) {
+ o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI;
+ continue;
+ }
+ if (driver_streq(a, "-mconsole")) {
+ o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI;
+ continue;
+ }
+ if (driver_strneq(a, "-mcmodel=", 9)) {
+ if (build_record_mcmodel(o, a + 9) != 0) return 1;
+ continue;
+ }
+
+ /* Target. */
+ if (driver_streq(a, "-target") || driver_streq(a, "--target")) {
+ if (++i >= argc) {
+ driver_errf(o->tool, "%.*s requires an argument",
+ KIT_SLICE_ARG(kit_slice_cstr(a)));
+ return 1;
+ }
+ if (driver_target_from_triple(argv[i], &o->target) != 0) {
+ driver_errf(o->tool, "unrecognized target triple: %.*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(o->tool, "unrecognized target triple: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(a + 9)));
+ return 1;
+ }
+ continue;
+ }
+ {
+ int tr = driver_target_features_try_consume(&o->target_features, o->env,
+ o->tool, argc, argv, &i);
+ if (tr < 0) return 1;
+ if (tr > 0) continue;
+ }
+
+ /* Support dir / sysroot. */
+ if (driver_streq(a, "-isysroot") || driver_streq(a, "--sysroot")) {
+ if (++i >= argc) {
+ driver_errf(o->tool, "%.*s requires an argument",
+ KIT_SLICE_ARG(kit_slice_cstr(a)));
+ return 1;
+ }
+ o->sysroot = argv[i];
+ continue;
+ }
+ if (driver_strneq(a, "--sysroot=", 10)) {
+ o->sysroot = a + 10;
+ continue;
+ }
+ if (driver_streq(a, "--support-dir")) {
+ if (++i >= argc) {
+ driver_errf(o->tool, "--support-dir requires an argument");
+ return 1;
+ }
+ o->support_dir = argv[i];
+ continue;
+ }
+ if (driver_strneq(a, "--support-dir=", 14)) {
+ o->support_dir = a + 14;
+ continue;
+ }
+
+ if (driver_streq(a, "--")) {
+ for (++i; i < argc; ++i)
+ if (build_classify_positional(o, argv[i]) != 0) return 1;
+ break;
+ }
+ if (a[0] == '-' && a[1] != '\0') {
+ driver_errf(o->tool, "unknown flag: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(a)));
+ return 1;
+ }
+ if (build_classify_positional(o, a) != 0) return 1;
+ }
+ return 0;
+}
+
+/* ===================================================================== */
+/* hosted libc / runtime orchestration (build-exe; shared build-lib) */
+/* ===================================================================== */
+
+static int build_is_link_output(const BuildOptions* o) {
+ return o->kind == BUILD_OUT_EXE || (o->kind == BUILD_OUT_LIB && o->dynamic);
+}
+
+static int build_resolve_pending_libs(BuildOptions* o) {
+ uint32_t i;
+ for (i = 0; i < o->npending_libs; ++i) {
+ BuildPendingLib* pl = &o->pending_libs[i];
+ char* p;
+ size_t sz;
+ LibResolveKind kind;
+ LibResolveMode mode = (o->static_link || pl->link_mode == KIT_LM_STATIC)
+ ? LIB_RESOLVE_STATIC_ONLY
+ : LIB_RESOLVE_DYNAMIC_PREFER;
+ LibResolveOS resolve_os = (o->target.os == KIT_OS_WINDOWS)
+ ? LIB_RESOLVE_OS_WINDOWS
+ : LIB_RESOLVE_OS_POSIX;
+ if (driver_lib_resolve_for_os(o->env, pl->name, mode, resolve_os,
+ o->lib_search_paths, o->nlib_search_paths, &p,
+ &sz, &kind) != 0) {
+ driver_errf(o->tool, "library not found: -l%.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(pl->name)));
+ return 1;
+ }
+ if (kind == LIB_RESOLVE_KIND_SHARED || kind == LIB_RESOLVE_KIND_TBD) {
+ BuildDsoInput* d = &o->dsos[o->ndsos++];
+ d->path = p;
+ d->owned = 1;
+ d->owned_size = sz;
+ pl->resolved_kind = BUILD_LINK_DSO;
+ pl->resolved_index = o->ndsos - 1u;
+ } else {
+ BuildArchiveInput* ar = &o->archives[o->narchives++];
+ ar->path = p;
+ ar->owned = 1;
+ ar->owned_size = sz;
+ ar->whole_archive = pl->whole_archive;
+ ar->link_mode = pl->link_mode;
+ ar->group_id = pl->group_id;
+ pl->resolved_kind = BUILD_LINK_ARCHIVE;
+ pl->resolved_index = o->narchives - 1u;
+ }
+ }
+ return 0;
+}
+
+/* A sysroot on its own engages the hosted libc profile (matching clang/gcc).
+ * -ffreestanding / -nostdinc / -nostdlib opt back out; -dynamic (shared) keeps
+ * its standalone meaning. */
+static void build_enable_hosted_for_sysroot(BuildOptions* o) {
+ if (o->wants_hosted_libc || o->shared) return;
+ if (!o->sysroot || !o->sysroot[0]) return;
+ if (o->freestanding || o->nostdinc) return;
+ if (o->no_stdlib || o->no_defaultlibs) return;
+ o->wants_hosted_libc = 1;
+}
+
+static void build_apply_default_hosted_profile(BuildOptions* o) {
+ if (o->target.os != KIT_OS_WINDOWS || o->target.obj != KIT_OBJ_COFF) return;
+ if (o->no_stdlib || o->no_defaultlibs || o->wants_hosted_libc) return;
+ if (!o->sysroot || !o->sysroot[0]) return;
+ o->wants_hosted_libc = 1;
+}
+
+static int build_apply_hosted_profile(BuildOptions* o) {
+ DriverHostedRequest req;
+ uint32_t i;
+ uint32_t insert_pos = 0;
+ if (!o->wants_hosted_libc || o->shared) return 0;
+ if (o->no_stdlib || o->no_defaultlibs) {
+ driver_errf(o->tool,
+ "-lc hosted expansion is disabled by -nostdlib/-nodefaultlibs");
+ return 1;
+ }
+ {
+ DriverHostedRequest z = {0};
+ req = z;
+ }
+ req.env = o->env;
+ req.tool = o->tool;
+ req.target = o->target;
+ req.sysroot = o->sysroot;
+ req.static_link = o->static_link;
+ req.link_inputs = 1;
+ if (driver_hosted_resolve(&req, &o->hosted) != 0) return 1;
+ for (i = 0; i < o->hosted.nsystem_includes; ++i)
+ o->groups[0].cf.system_include_dirs[o->groups[0].cf.nsystem_include_dirs++] =
+ o->hosted.system_includes[i];
+ for (i = 0; i < o->hosted.ndefines; ++i)
+ o->groups[0].cf.defines[o->groups[0].cf.ndefines++] = o->hosted.defines[i];
+ for (i = 0; i < o->hosted.nbefore; ++i) {
+ if (o->no_startfiles) break;
+ if (build_append_hosted_input(o, &o->hosted.before[i], insert_pos, 1) != 0)
+ return 1;
+ insert_pos++;
+ }
+ for (i = 0; i < o->hosted.nafter; ++i)
+ if (build_append_hosted_input(o, &o->hosted.after[i], 0, 0) != 0) return 1;
+ for (i = 0; i < o->hosted.nfinal; ++i) {
+ if (o->no_startfiles) break;
+ if (build_append_hosted_input(o, &o->hosted.final[i], 0, 0) != 0) return 1;
+ }
+ if (!o->interp_path && o->hosted.interp_path)
+ o->interp_path = o->hosted.interp_path;
+ return 0;
+}
+
+/* Append `<sysroot>/lib` to the search path for Windows targets (mirrors cc). */
+static int build_append_windows_lib_dirs(BuildOptions* o) {
+ const char* sysroot = o->sysroot;
+ char* joined;
+ size_t srlen, need_slash, bytes, off = 0;
+ if (o->target.os != KIT_OS_WINDOWS) return 0;
+ if (!sysroot || !sysroot[0]) {
+ sysroot = driver_getenv("KIT_SYSROOT");
+ if (!sysroot || !sysroot[0]) return 0;
+ o->sysroot = sysroot;
+ }
+ srlen = driver_strlen(sysroot);
+ need_slash = (srlen > 0 && sysroot[srlen - 1] != '/') ? 1u : 0u;
+ bytes = srlen + need_slash + 3u + 1u;
+ joined = driver_alloc(o->env, bytes);
+ if (!joined) {
+ driver_errf(o->tool, "out of memory");
+ return 1;
+ }
+ driver_memcpy(joined + off, sysroot, srlen);
+ off += srlen;
+ if (need_slash) joined[off++] = '/';
+ driver_memcpy(joined + off, "lib", 3);
+ off += 3;
+ joined[off] = '\0';
+ o->owned_sysroot_lib_dir = joined;
+ o->owned_sysroot_lib_dir_size = bytes;
+ o->lib_search_paths[o->nlib_search_paths++] = joined;
+ return 0;
+}
+
+/* ===================================================================== */
+/* merged preprocessor view per group */
+/* ===================================================================== */
+
+static int build_group_build_pp(BuildOptions* o, uint32_t gi) {
+ BuildGroup* g = &o->groups[gi];
+ BuildGroup* gl = &o->groups[0];
+ uint32_t k;
+ if (gi == 0) {
+ driver_cflags_fill_pp(&gl->cf, &g->pp);
+ return 0;
+ }
+ /* include = group then global (group searched first). */
+ g->m_ninc = g->cf.ninclude_dirs + gl->cf.ninclude_dirs;
+ g->m_nsys = g->cf.nsystem_include_dirs + gl->cf.nsystem_include_dirs;
+ g->m_def_cap = gl->cf.ndefines + g->cf.ndefines;
+ g->m_nund = gl->cf.nundefines + g->cf.nundefines;
+ if (g->m_ninc)
+ g->m_inc = driver_alloc_zeroed(o->env, g->m_ninc * sizeof(*g->m_inc));
+ if (g->m_nsys)
+ g->m_sys = driver_alloc_zeroed(o->env, g->m_nsys * sizeof(*g->m_sys));
+ if (g->m_def_cap)
+ g->m_def = driver_alloc_zeroed(o->env, g->m_def_cap * sizeof(*g->m_def));
+ if (g->m_nund)
+ g->m_und = driver_alloc_zeroed(o->env, g->m_nund * sizeof(*g->m_und));
+ if ((g->m_ninc && !g->m_inc) || (g->m_nsys && !g->m_sys) ||
+ (g->m_def_cap && !g->m_def) || (g->m_nund && !g->m_und)) {
+ driver_errf(o->tool, "out of memory");
+ return 1;
+ }
+ {
+ uint32_t p = 0, j;
+ for (k = 0; k < g->cf.ninclude_dirs; ++k) g->m_inc[p++] = g->cf.include_dirs[k];
+ for (k = 0; k < gl->cf.ninclude_dirs; ++k) g->m_inc[p++] = gl->cf.include_dirs[k];
+ p = 0;
+ for (k = 0; k < g->cf.nsystem_include_dirs; ++k)
+ g->m_sys[p++] = g->cf.system_include_dirs[k];
+ for (k = 0; k < gl->cf.nsystem_include_dirs; ++k)
+ g->m_sys[p++] = gl->cf.system_include_dirs[k];
+ /* defines: each global define survives only if the group does not redefine
+ * the same name; the group's defines then follow. This realizes "a group
+ * -D of an already-defined name overrides it for that group" without
+ * emitting a duplicate (which the preprocessor rejects as a conflicting
+ * redefinition). */
+ p = 0;
+ for (k = 0; k < gl->cf.ndefines; ++k) {
+ int shadowed = 0;
+ for (j = 0; j < g->cf.ndefines; ++j) {
+ if (kit_slice_eq(gl->cf.defines[k].name, g->cf.defines[j].name)) {
+ shadowed = 1;
+ break;
+ }
+ }
+ if (!shadowed) g->m_def[p++] = gl->cf.defines[k];
+ }
+ for (k = 0; k < g->cf.ndefines; ++k) g->m_def[p++] = g->cf.defines[k];
+ g->m_ndef = p;
+ p = 0;
+ for (k = 0; k < gl->cf.nundefines; ++k) g->m_und[p++] = gl->cf.undefines[k];
+ for (k = 0; k < g->cf.nundefines; ++k) g->m_und[p++] = g->cf.undefines[k];
+ }
+ {
+ KitPreprocessOptions z = {0};
+ g->pp = z;
+ g->pp.include_dirs = g->m_inc;
+ g->pp.ninclude_dirs = g->m_ninc;
+ g->pp.system_include_dirs = g->m_sys;
+ g->pp.nsystem_include_dirs = g->m_nsys;
+ g->pp.defines = g->m_def;
+ g->pp.ndefines = g->m_ndef;
+ g->pp.undefines = g->m_und;
+ g->pp.nundefines = g->m_nund;
+ }
+ return 0;
+}
+
+/* ===================================================================== */
+/* compile one source */
+/* ===================================================================== */
+
+static KitLanguage build_resolve_lang(BuildOptions* o, KitCompiler* compiler,
+ uint32_t si) {
+ uint32_t gi = o->sources[si].group;
+ if (gi != 0 && o->groups[gi].forced_lang != BUILD_LANG_AUTO)
+ return o->groups[gi].forced_lang;
+ if (o->groups[0].forced_lang != BUILD_LANG_AUTO)
+ return o->groups[0].forced_lang;
+ return kit_language_for_path(compiler, o->sources[si].path);
+}
+
+/* Collect the -X<lang> frontend tokens for a source into a fresh argv:
+ * global (groups[0]) tokens first, then the source's group's. */
+static int build_collect_fe_argv(BuildOptions* o, uint32_t si, KitLanguage lang,
+ char*** out_argv, uint32_t* out_n) {
+ uint32_t gi = o->sources[si].group;
+ uint32_t n = 0, k, p = 0;
+ char** argv;
+ for (k = 0; k < o->groups[0].nfe; ++k)
+ if (o->groups[0].fe[k].lang == (uint8_t)lang) n++;
+ if (gi != 0)
+ for (k = 0; k < o->groups[gi].nfe; ++k)
+ if (o->groups[gi].fe[k].lang == (uint8_t)lang) n++;
+ *out_argv = NULL;
+ *out_n = 0;
+ if (n == 0) return 0;
+ argv = driver_alloc_zeroed(o->env, n * sizeof(*argv));
+ if (!argv) {
+ driver_errf(o->tool, "out of memory");
+ return 1;
+ }
+ for (k = 0; k < o->groups[0].nfe; ++k)
+ if (o->groups[0].fe[k].lang == (uint8_t)lang)
+ argv[p++] = o->groups[0].fe[k].tok;
+ if (gi != 0)
+ for (k = 0; k < o->groups[gi].nfe; ++k)
+ if (o->groups[gi].fe[k].lang == (uint8_t)lang)
+ argv[p++] = o->groups[gi].fe[k].tok;
+ *out_argv = argv;
+ *out_n = n;
+ return 0;
+}
+
+/* Compile source[si]; emit to `emit_out` (per-source output) or return a
+ * builder via `obj_out` (link/archive). Exactly one of the two is non-NULL. */
+static int build_compile_source(BuildOptions* o, KitCompiler* compiler,
+ const KitContext* ctx, uint32_t si,
+ const KitCodeOptions* code,
+ const KitDiagnosticOptions* diag,
+ KitWriter* emit_out, KitObjBuilder** obj_out) {
+ const char* path = o->sources[si].path;
+ uint32_t gi = o->sources[si].group;
+ DriverLoad load = {0};
+ KitSlice bytes = {0};
+ KitLanguage lang;
+ char** fe_argv = NULL;
+ uint32_t fe_n = 0;
+ void* lang_extra = NULL;
+ KitStatus st;
+ int rc = 1;
+
+ if (driver_load_bytes(ctx->file_io, o->tool, path, &load, &bytes) != 0)
+ return 1;
+
+ lang = build_resolve_lang(o, compiler, si);
+ if (lang == KIT_LANG_UNKNOWN) {
+ driver_errf(o->tool, "cannot determine language for %.*s (use -x LANG)",
+ KIT_SLICE_ARG(kit_slice_cstr(path)));
+ goto out;
+ }
+ if (build_collect_fe_argv(o, si, lang, &fe_argv, &fe_n) != 0) goto out;
+ if (fe_n) {
+ if (kit_frontend_parse_options(compiler, lang, (int)fe_n, fe_argv,
+ &lang_extra) != KIT_OK) {
+ driver_errf(o->tool, "unsupported -X%.*s frontend flag: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(
+ lang == KIT_LANG_C ? "c"
+ : lang == KIT_LANG_ASM ? "asm"
+ : lang == KIT_LANG_TOY ? "toy"
+ : "wasm")),
+ KIT_SLICE_ARG(kit_slice_cstr(fe_argv[0])));
+ goto out;
+ }
+ }
+
+ st = driver_compile_run(compiler, lang, code, diag, &o->groups[gi].pp,
+ lang_extra, kit_slice_cstr(path), &bytes, emit_out,
+ obj_out);
+ rc = (st == KIT_OK) ? 0 : 1;
+
+out:
+ if (lang_extra) kit_frontend_free_options(compiler, lang, lang_extra);
+ if (fe_argv) driver_free(o->env, fe_argv, fe_n * sizeof(*fe_argv));
+ driver_release_bytes(ctx->file_io, &load);
+ return rc;
+}
+
+static void build_fill_code(const BuildOptions* o, KitCodeOptions* code) {
+ KitCodeOptions z = {0};
+ *code = z;
+ code->opt_level = o->syntax_only ? 0 : o->opt_level;
+ code->debug_info = o->debug_info ? true : false;
+ code->check_only = o->syntax_only ? true : false;
+ code->default_visibility = o->default_visibility;
+ code->function_sections = o->function_sections ? true : false;
+ code->data_sections = o->data_sections ? true : false;
+ code->epoch = o->epoch;
+}
+
+/* ===================================================================== */
+/* default output naming */
+/* ===================================================================== */
+
+static char* build_default_obj_name(DriverEnv* env, const BuildOptions* o,
+ const char* src, size_t* out_size) {
+ int win = (o->target.os == KIT_OS_WINDOWS);
+ const char* ext;
+ size_t ext_len, srclen = driver_strlen(src), dot = driver_strlen(src),
+ slash = 0, k;
+ switch ((BuildEmit)o->emit) {
+ case BUILD_EMIT_ASM:
+ ext = ".s";
+ ext_len = 2u;
+ break;
+ case BUILD_EMIT_IR:
+ ext = ".ir";
+ ext_len = 3u;
+ break;
+ case BUILD_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;
+ }
+}
+
+/* Open the output: a file, or stdout for "-". */
+static int build_open_output(const KitContext* ctx, DriverEnv* env,
+ const char* tool, const char* path,
+ KitWriter** out) {
+ if (driver_streq(path, "-")) {
+ *out = driver_stdout_writer(env);
+ if (!*out) {
+ driver_errf(tool, "out of memory");
+ return 1;
+ }
+ return 0;
+ }
+ if (ctx->file_io->open_writer(ctx->file_io->user, path, out) != KIT_OK) {
+ driver_errf(tool, "failed to open output: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(path)));
+ return 1;
+ }
+ return 0;
+}
+
+/* ===================================================================== */
+/* run paths */
+/* ===================================================================== */
+
+/* Build a KitLinkSessionOptions from the parsed link knobs. */
+static int build_fill_link_opts(BuildOptions* o, DriverEnv* env,
+ KitLinkScript* script, uint8_t output_kind,
+ KitLinkSessionOptions* lopts,
+ KitSlice** rpath_slices_out) {
+ KitSlice* rpath_slices = NULL;
+ uint32_t i;
+ if (o->nrpaths) {
+ rpath_slices = driver_alloc_zeroed(env, o->nrpaths * sizeof(*rpath_slices));
+ if (!rpath_slices) {
+ driver_errf(o->tool, "out of memory");
+ return 1;
+ }
+ for (i = 0; i < o->nrpaths; ++i)
+ rpath_slices[i] = kit_slice_cstr(o->rpaths[i]);
+ }
+ memset(lopts, 0, sizeof(*lopts));
+ lopts->output_kind = output_kind;
+ lopts->entry = kit_slice_cstr(o->entry);
+ lopts->linker_script = script;
+ lopts->build_id_mode = o->build_id_mode;
+ lopts->build_id_bytes = o->build_id_bytes;
+ lopts->build_id_len = o->build_id_len;
+ lopts->gc_sections = o->gc_sections ? true : false;
+ lopts->strip_debug = o->strip_debug ? true : false;
+ lopts->pie = driver_link_pie(o->target, o->pie, o->shared,
+ output_kind == KIT_LINK_OUTPUT_RELOCATABLE)
+ ? true
+ : false;
+ lopts->pe_subsystem = o->pe_subsystem;
+ lopts->interp_path = kit_slice_cstr(o->interp_path);
+ lopts->soname = kit_slice_cstr(o->soname);
+ if (o->new_dtags) {
+ lopts->runpaths = rpath_slices;
+ lopts->nrunpaths = o->nrpaths;
+ } else {
+ lopts->rpaths = rpath_slices;
+ lopts->nrpaths = o->nrpaths;
+ }
+ lopts->allow_undefined = 1;
+ *rpath_slices_out = rpath_slices;
+ return 0;
+}
+
+/* Compile every source to an in-memory builder; objs[] is caller-owned. */
+static int build_compile_all(BuildOptions* o, KitCompiler* compiler,
+ const KitContext* ctx, const KitCodeOptions* code,
+ const KitDiagnosticOptions* diag,
+ KitObjBuilder** objs) {
+ uint32_t i;
+ for (i = 0; i < o->nsources; ++i) {
+ if (build_compile_source(o, compiler, ctx, i, code, diag, NULL,
+ &objs[i]) != 0)
+ return 1;
+ }
+ return 0;
+}
+
+/* build-exe / shared build-lib: compile sources, load link inputs, link. */
+static int build_run_link(BuildOptions* o, KitCompiler* compiler,
+ const KitContext* ctx, const KitCodeOptions* code,
+ const KitDiagnosticOptions* diag, uint8_t output_kind) {
+ DriverEnv* env = o->env;
+ const KitFileIO* io = ctx->file_io;
+ KitWriter* out_w = NULL;
+ DriverLoad* obj_lf = NULL;
+ DriverLoad* arch_lf = NULL;
+ DriverLoad* dso_lf = NULL;
+ DriverLoad script_lf = {0};
+ KitSlice* obj_in = NULL;
+ KitSlice* obj_names = NULL;
+ KitLinkArchiveInput* arch_in = NULL;
+ KitSlice* dso_in = NULL;
+ KitSlice* dso_names = NULL;
+ KitLinkInputOrder* order = NULL;
+ KitObjBuilder** objs = NULL;
+ KitLinkScript* script = NULL;
+ KitSlice* rpath_slices = NULL;
+ uint32_t i;
+ int rc = 1;
+
+ if (o->nsources) {
+ objs = driver_alloc_zeroed(env, o->nsources * sizeof(*objs));
+ if (!objs) goto oom;
+ }
+ if (o->nobject_files) {
+ obj_lf = driver_alloc_zeroed(env, o->nobject_files * sizeof(*obj_lf));
+ obj_in = driver_alloc_zeroed(env, o->nobject_files * sizeof(*obj_in));
+ obj_names = driver_alloc_zeroed(env, o->nobject_files * sizeof(*obj_names));
+ if (!obj_lf || !obj_in || !obj_names) goto oom;
+ }
+ if (o->narchives) {
+ arch_lf = driver_alloc_zeroed(env, o->narchives * sizeof(*arch_lf));
+ arch_in = driver_alloc_zeroed(env, o->narchives * sizeof(*arch_in));
+ if (!arch_lf || !arch_in) goto oom;
+ }
+ if (o->ndsos) {
+ dso_lf = driver_alloc_zeroed(env, o->ndsos * sizeof(*dso_lf));
+ dso_in = driver_alloc_zeroed(env, o->ndsos * sizeof(*dso_in));
+ dso_names = driver_alloc_zeroed(env, o->ndsos * sizeof(*dso_names));
+ if (!dso_lf || !dso_in || !dso_names) goto oom;
+ }
+ if (o->nlink_items) {
+ order = driver_alloc_zeroed(env, o->nlink_items * sizeof(*order));
+ if (!order) goto oom;
+ }
+
+ for (i = 0; i < o->nobject_files; ++i) {
+ if (driver_load_bytes(io, o->tool, o->object_files[i], &obj_lf[i],
+ &obj_in[i]) != 0)
+ goto out;
+ obj_names[i] = kit_slice_cstr(o->object_files[i]);
+ }
+ for (i = 0; i < o->narchives; ++i) {
+ if (driver_load_bytes(io, o->tool, o->archives[i].path, &arch_lf[i],
+ &arch_in[i].bytes) != 0)
+ goto out;
+ arch_in[i].name = kit_slice_cstr(o->archives[i].path);
+ arch_in[i].link_mode = o->archives[i].link_mode;
+ arch_in[i].whole_archive = o->archives[i].whole_archive ? true : false;
+ arch_in[i].group_id = o->archives[i].group_id;
+ }
+ for (i = 0; i < o->ndsos; ++i) {
+ if (driver_load_bytes(io, o->tool, o->dsos[i].path, &dso_lf[i],
+ &dso_in[i]) != 0)
+ goto out;
+ dso_names[i] = kit_slice_cstr(o->dsos[i].path);
+ }
+ if (o->linker_script) {
+ KitSlice dummy;
+ if (driver_load_bytes(io, o->tool, o->linker_script, &script_lf, &dummy) !=
+ 0)
+ goto out;
+ }
+
+ if (script_lf.loaded) {
+ KitSlice text = {.s = (const char*)script_lf.fd.data,
+ .len = script_lf.fd.size};
+ if (kit_link_script_parse(ctx, text, &script) != KIT_OK) goto out;
+ }
+
+ if (build_compile_all(o, compiler, ctx, code, diag, objs) != 0) goto out;
+
+ if (build_open_output(ctx, env, o->tool, o->output_path, &out_w) != 0)
+ goto out;
+
+ /* Translate the recorded link order into KitLinkInputOrder. */
+ for (i = 0; i < o->nlink_items; ++i) {
+ const BuildLinkItem* item = &o->link_items[i];
+ KitLinkInputOrder* ord = &order[i];
+ switch ((BuildLinkKind)item->kind) {
+ case BUILD_LINK_SOURCE:
+ ord->kind = KIT_LINK_INPUT_OBJ;
+ ord->index = item->index;
+ break;
+ case BUILD_LINK_OBJECT:
+ ord->kind = KIT_LINK_INPUT_OBJ_BYTES;
+ ord->index = item->index;
+ break;
+ case BUILD_LINK_ARCHIVE:
+ ord->kind = KIT_LINK_INPUT_ARCHIVE;
+ ord->index = item->index;
+ break;
+ case BUILD_LINK_DSO:
+ ord->kind = KIT_LINK_INPUT_DSO;
+ ord->index = item->index;
+ break;
+ case BUILD_LINK_LIB: {
+ const BuildPendingLib* pl = &o->pending_libs[item->index];
+ if (pl->resolved_kind == BUILD_LINK_DSO) {
+ ord->kind = KIT_LINK_INPUT_DSO;
+ ord->index = pl->resolved_index;
+ } else {
+ ord->kind = KIT_LINK_INPUT_ARCHIVE;
+ ord->index = pl->resolved_index;
+ }
+ break;
+ }
+ }
+ }
+
+ {
+ KitLinkSessionOptions lopts;
+ DriverLinkInputs li;
+ KitStatus st;
+ if (build_fill_link_opts(o, env, script, output_kind, &lopts,
+ &rpath_slices) != 0)
+ goto out;
+ memset(&li, 0, sizeof(li));
+ li.objs = objs;
+ li.nobjs = o->nsources;
+ li.obj_names = obj_names;
+ li.obj_bytes = obj_in;
+ li.nobj_bytes = o->nobject_files;
+ li.archives = arch_in;
+ li.narchives = o->narchives;
+ li.dso_names = dso_names;
+ li.dso_bytes = dso_in;
+ li.ndsos = o->ndsos;
+ li.order = order;
+ li.norder = o->nlink_items;
+ st = driver_link_engine_emit(compiler, &lopts, &li, out_w);
+ rc = (st == KIT_OK) ? 0 : 1;
+ }
+
+out:
+ if (out_w) kit_writer_close(out_w);
+ if (rc == 0 && output_kind == KIT_LINK_OUTPUT_EXE &&
+ !driver_streq(o->output_path, "-")) {
+ if (driver_mark_executable_output(o->output_path) != 0) {
+ driver_errf(o->tool, "failed to set executable mode: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(o->output_path)));
+ rc = 1;
+ }
+ }
+ if (script) kit_link_script_free(ctx, script);
+ if (rpath_slices)
+ driver_free(env, rpath_slices, o->nrpaths * sizeof(*rpath_slices));
+ driver_release_bytes(io, &script_lf);
+ if (arch_lf)
+ for (i = 0; i < o->narchives; ++i) driver_release_bytes(io, &arch_lf[i]);
+ if (dso_lf)
+ for (i = 0; i < o->ndsos; ++i) driver_release_bytes(io, &dso_lf[i]);
+ if (obj_lf)
+ for (i = 0; i < o->nobject_files; ++i) driver_release_bytes(io, &obj_lf[i]);
+ if (arch_in) driver_free(env, arch_in, o->narchives * sizeof(*arch_in));
+ if (arch_lf) driver_free(env, arch_lf, o->narchives * sizeof(*arch_lf));
+ if (dso_in) driver_free(env, dso_in, o->ndsos * sizeof(*dso_in));
+ if (dso_names) driver_free(env, dso_names, o->ndsos * sizeof(*dso_names));
+ if (dso_lf) driver_free(env, dso_lf, o->ndsos * sizeof(*dso_lf));
+ if (order) driver_free(env, order, o->nlink_items * sizeof(*order));
+ if (obj_in) driver_free(env, obj_in, o->nobject_files * sizeof(*obj_in));
+ if (obj_names)
+ driver_free(env, obj_names, o->nobject_files * sizeof(*obj_names));
+ if (obj_lf) driver_free(env, obj_lf, o->nobject_files * sizeof(*obj_lf));
+ /* The link session borrows the per-source builders (it frees only its own
+ * pointer array), so the caller still owns and must release them. */
+ if (objs) {
+ for (i = 0; i < o->nsources; ++i) kit_obj_builder_free(objs[i]);
+ driver_free(env, objs, o->nsources * sizeof(*objs));
+ }
+ return rc;
+
+oom:
+ driver_errf(o->tool, "out of memory");
+ goto out;
+}
+
+/* build-obj multi-source: combine into one relocatable object (ld -r). */
+static int build_run_relocatable(BuildOptions* o, KitCompiler* compiler,
+ const KitContext* ctx,
+ const KitCodeOptions* code,
+ const KitDiagnosticOptions* diag) {
+ DriverEnv* env = o->env;
+ KitObjBuilder** objs = NULL;
+ KitLinkInputOrder* order = NULL;
+ KitWriter* out_w = NULL;
+ uint32_t i;
+ int rc = 1;
+
+ objs = driver_alloc_zeroed(env, o->nsources * sizeof(*objs));
+ order = driver_alloc_zeroed(env, o->nsources * sizeof(*order));
+ if (!objs || !order) {
+ driver_errf(o->tool, "out of memory");
+ goto out;
+ }
+ if (build_compile_all(o, compiler, ctx, code, diag, objs) != 0) goto out;
+ for (i = 0; i < o->nsources; ++i) {
+ order[i].kind = KIT_LINK_INPUT_OBJ;
+ order[i].index = i;
+ }
+ if (build_open_output(ctx, env, o->tool, o->output_path, &out_w) != 0)
+ goto out;
+ {
+ KitLinkSessionOptions lopts;
+ DriverLinkInputs li;
+ KitStatus st;
+ memset(&lopts, 0, sizeof(lopts));
+ lopts.output_kind = KIT_LINK_OUTPUT_RELOCATABLE;
+ lopts.allow_undefined = 1;
+ lopts.strip_debug = o->strip_debug ? true : false;
+ memset(&li, 0, sizeof(li));
+ li.objs = objs;
+ li.nobjs = o->nsources;
+ li.order = order;
+ li.norder = o->nsources;
+ st = driver_link_engine_emit(compiler, &lopts, &li, out_w);
+ rc = (st == KIT_OK) ? 0 : 1;
+ }
+
+out:
+ if (out_w) kit_writer_close(out_w);
+ if (order) driver_free(env, order, o->nsources * sizeof(*order));
+ /* The link session borrows the builders; release them here (see
+ * build_run_link). */
+ if (objs) {
+ for (i = 0; i < o->nsources; ++i) kit_obj_builder_free(objs[i]);
+ driver_free(env, objs, o->nsources * sizeof(*objs));
+ }
+ return rc;
+}
+
+/* build-lib static: archive every compiled source object. */
+static int build_run_archive(BuildOptions* o, KitCompiler* compiler,
+ const KitContext* ctx, const KitCodeOptions* code,
+ const KitDiagnosticOptions* diag) {
+ DriverEnv* env = o->env;
+ KitObjBuilder** objs = NULL;
+ KitSlice* names = NULL;
+ char** owned_names = NULL;
+ size_t* owned_name_sizes = NULL;
+ KitWriter* out_w = NULL;
+ uint32_t i;
+ int rc = 1;
+
+ objs = driver_alloc_zeroed(env, o->nsources * sizeof(*objs));
+ names = driver_alloc_zeroed(env, o->nsources * sizeof(*names));
+ owned_names = driver_alloc_zeroed(env, o->nsources * sizeof(*owned_names));
+ owned_name_sizes =
+ driver_alloc_zeroed(env, o->nsources * sizeof(*owned_name_sizes));
+ if (!objs || !names || !owned_names || !owned_name_sizes) {
+ driver_errf(o->tool, "out of memory");
+ goto out;
+ }
+ if (build_compile_all(o, compiler, ctx, code, diag, objs) != 0) goto out;
+ /* build-lib always emits objects (validated), so build_default_obj_name
+ * yields the right `.o`/`.obj` member name. */
+ for (i = 0; i < o->nsources; ++i) {
+ owned_names[i] =
+ build_default_obj_name(env, o, o->sources[i].path, &owned_name_sizes[i]);
+ if (!owned_names[i]) {
+ driver_errf(o->tool, "out of memory");
+ goto out;
+ }
+ names[i] = kit_slice_cstr(owned_names[i]);
+ }
+ if (build_open_output(ctx, env, o->tool, o->output_path, &out_w) != 0)
+ goto out;
+ rc = driver_archive_emit(env, ctx, o->tool, objs, names, o->nsources,
+ o->epoch, out_w);
+
+out:
+ if (out_w) kit_writer_close(out_w);
+ if (objs)
+ for (i = 0; i < o->nsources; ++i) kit_obj_builder_free(objs[i]);
+ if (owned_names) {
+ for (i = 0; i < o->nsources; ++i)
+ if (owned_names[i])
+ driver_free(env, owned_names[i], owned_name_sizes[i]);
+ }
+ if (objs) driver_free(env, objs, o->nsources * sizeof(*objs));
+ if (names) driver_free(env, names, o->nsources * sizeof(*names));
+ if (owned_names)
+ driver_free(env, owned_names, o->nsources * sizeof(*owned_names));
+ if (owned_name_sizes)
+ driver_free(env, owned_name_sizes, o->nsources * sizeof(*owned_name_sizes));
+ return rc;
+}
+
+/* build-obj per-source: check-only, or one output per source (obj/asm/c/ir). */
+static int build_run_per_source(BuildOptions* o, KitCompiler* compiler,
+ const KitContext* ctx,
+ const KitCodeOptions* code_in,
+ const KitDiagnosticOptions* diag) {
+ DriverEnv* env = o->env;
+ KitCodeOptions code = *code_in;
+ uint32_t i;
+
+ code.emit_asm_source = (o->emit == BUILD_EMIT_ASM) ? true : false;
+ code.emit_c_source = (o->emit == BUILD_EMIT_C) ? true : false;
+ code.emit_ir = (o->emit == BUILD_EMIT_IR) ? true : false;
+
+ for (i = 0; i < o->nsources; ++i) {
+ if (o->syntax_only) {
+ KitObjBuilder* ob = NULL;
+ int rc = build_compile_source(o, compiler, ctx, i, &code, diag, NULL, &ob);
+ kit_obj_builder_free(ob);
+ if (rc != 0) return rc;
+ continue;
+ }
+ {
+ const char* out_path = o->output_path;
+ char* owned = NULL;
+ size_t owned_size = 0;
+ KitWriter* w = NULL;
+ int rc;
+ if (!out_path) {
+ owned = build_default_obj_name(env, o, o->sources[i].path, &owned_size);
+ if (!owned) {
+ driver_errf(o->tool, "out of memory");
+ return 1;
+ }
+ out_path = owned;
+ }
+ if (build_open_output(ctx, env, o->tool, out_path, &w) != 0) {
+ if (owned) driver_free(env, owned, owned_size);
+ return 1;
+ }
+ rc = build_compile_source(o, compiler, ctx, i, &code, diag, w, NULL);
+ kit_writer_close(w);
+ if (owned) driver_free(env, owned, owned_size);
+ if (rc != 0) return rc;
+ }
+ }
+ return 0;
+}
+
+/* ===================================================================== */
+/* validation */
+/* ===================================================================== */
+
+static int build_validate(BuildOptions* o) {
+ uint32_t total_link = o->nobject_files + o->narchives + o->ndsos +
+ o->npending_libs;
+
+ if (o->nsources == 0 && total_link == 0) {
+ driver_errf(o->tool, "no input files");
+ return 1;
+ }
+
+ /* -dynamic/-shared selects a shared library for build-lib; it is a no-op for
+ * build-exe (already dynamically linked by default) and an error for
+ * build-obj (caught in its branch below). */
+
+ if (o->kind == BUILD_OUT_EXE) {
+ if (o->emit != BUILD_EMIT_OBJ || o->syntax_only) {
+ driver_errf(o->tool, "--emit/-S/-fsyntax-only are build-obj options");
+ return 1;
+ }
+ if (!o->output_path)
+ o->output_path = (o->target.os == KIT_OS_WINDOWS) ? "a.exe" : "a.out";
+ return 0;
+ }
+
+ if (o->kind == BUILD_OUT_LIB) {
+ if (o->dynamic) {
+ driver_errf(o->tool,
+ "creating dynamic/shared libraries is not yet supported; "
+ "build-lib produces a static .a");
+ return 1;
+ }
+ if (o->emit != BUILD_EMIT_OBJ || o->syntax_only) {
+ driver_errf(o->tool, "--emit/-S/-fsyntax-only are build-obj options");
+ return 1;
+ }
+ if (total_link != 0) {
+ driver_errf(o->tool,
+ "build-lib takes only sources; pass .o/.a/-l to build-exe");
+ return 1;
+ }
+ if (o->nsources == 0) {
+ driver_errf(o->tool, "no source files");
+ return 1;
+ }
+ if (!o->output_path) {
+ driver_errf(o->tool, "-o is required (no default library name)");
+ return 1;
+ }
+ return 0;
+ }
+
+ /* build-obj */
+ if (total_link != 0) {
+ driver_errf(o->tool,
+ "build-obj takes only sources; pass .o/.a/-l to build-exe");
+ return 1;
+ }
+ if (o->nsources == 0) {
+ driver_errf(o->tool, "no source files");
+ return 1;
+ }
+ if (o->dynamic) {
+ driver_errf(o->tool, "-dynamic/-shared is only valid for build-lib");
+ return 1;
+ }
+ if (o->syntax_only) {
+ if (o->output_path) {
+ driver_errf(o->tool, "-o is incompatible with -fsyntax-only");
+ return 1;
+ }
+ return 0;
+ }
+ if (o->emit == BUILD_EMIT_IR && o->opt_level < 1) {
+ driver_errf(o->tool,
+ "--emit=ir requires -O1 or higher (the IR tape is only "
+ "recorded when the optimizer runs)");
+ return 1;
+ }
+ if (o->emit == BUILD_EMIT_OBJ && o->nsources > 1) {
+ /* relocatable combine */
+ if (!o->output_path) {
+ driver_errf(o->tool,
+ "-o is required to combine multiple sources into one object");
+ return 1;
+ }
+ return 0;
+ }
+ /* per-source emit (single obj, or asm/c/ir) */
+ if (o->output_path && o->nsources > 1) {
+ driver_errf(o->tool, "-o cannot be used with multiple sources");
+ return 1;
+ }
+ if (o->emit == BUILD_EMIT_C && !o->output_path) {
+ driver_errf(o->tool, "--emit=c requires -o");
+ return 1;
+ }
+ return 0;
+}
+
+/* ===================================================================== */
+/* driver */
+/* ===================================================================== */
+
+static int build_apply_env(BuildOptions* o) {
+ const char* sde = driver_getenv("SOURCE_DATE_EPOCH");
+ if (sde && build_parse_u64(sde, &o->epoch) != 0) {
+ driver_errf(o->tool, "invalid SOURCE_DATE_EPOCH: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(sde)));
+ return 1;
+ }
+ return 0;
+}
+
+static int build_main(int argc, char** argv, int kind, const char* tool) {
+ DriverEnv env;
+ BuildOptions o = {0};
+ DriverRuntimeSupport runtime = {0};
+ int runtime_resolved = 0;
+ KitContext ctx;
+ KitTarget* target = NULL;
+ KitCompiler* compiler = NULL;
+ KitCodeOptions code;
+ KitDiagnosticOptions diag = {0};
+ uint32_t gi;
+ int rc = 2;
+
+ driver_env_init(&env);
+ o.env = &env;
+ o.tool = tool;
+ o.kind = kind;
+ o.driver_path = argv[0];
+
+ if (build_alloc(&o, argc) != 0) {
+ rc = 2;
+ goto done;
+ }
+ if (build_parse(argc, argv, &o) != 0) goto done;
+ if (build_apply_env(&o) != 0) goto done;
+
+ o.shared = (o.kind == BUILD_OUT_LIB && o.dynamic);
+ if (o.shared && o.target.pic == KIT_PIC_NONE) o.target.pic = KIT_PIC_PIC;
+
+ if (build_validate(&o) != 0) goto done;
+
+ /* Freestanding runtime headers so C/asm #includes resolve (mirrors compile);
+ * for link outputs, also the runtime archive + hosted libc wiring. */
+ if (driver_runtime_resolve(&env, o.support_dir, o.driver_path, &runtime) ==
+ 0) {
+ runtime_resolved = 1;
+ if (driver_runtime_add_freestanding_headers(&runtime, &o.groups[0].cf) !=
+ 0) {
+ driver_errf(tool, "failed to add freestanding headers");
+ rc = 1;
+ goto done;
+ }
+ } else {
+ driver_errf(tool, "support dir not found");
+ rc = 1;
+ goto done;
+ }
+
+ if (build_is_link_output(&o)) {
+ if (build_append_windows_lib_dirs(&o) != 0) {
+ rc = 1;
+ goto done;
+ }
+ build_enable_hosted_for_sysroot(&o);
+ build_apply_default_hosted_profile(&o);
+ if (build_apply_hosted_profile(&o) != 0) {
+ rc = 1;
+ goto done;
+ }
+ if (build_resolve_pending_libs(&o) != 0) {
+ rc = 1;
+ goto done;
+ }
+ if (!o.no_stdlib && !o.no_defaultlibs) {
+ DriverRuntimeArchive rt = {0};
+ uint32_t insert_pos;
+ if (driver_runtime_prepare_archive(&env, tool, &runtime, o.target, o.epoch,
+ &rt) != 0) {
+ driver_runtime_archive_fini(&env, &rt);
+ rc = 1;
+ goto done;
+ }
+ insert_pos = o.nlink_items;
+ if (o.hosted.nfinal <= insert_pos) insert_pos -= o.hosted.nfinal;
+ build_insert_runtime_archive(&o, &rt, insert_pos);
+ driver_runtime_archive_fini(&env, &rt);
+ }
+ } else if (o.npending_libs) {
+ driver_errf(tool, "-l is only valid for build-exe");
+ rc = 1;
+ goto done;
+ }
+
+ /* Build per-group merged preprocessor views now that the global baseline
+ * (groups[0]) holds the runtime/hosted includes and defines. */
+ for (gi = 0; gi < o.ngroups; ++gi) {
+ if (build_group_build_pp(&o, gi) != 0) {
+ rc = 1;
+ goto done;
+ }
+ }
+
+ ctx = driver_env_to_context(&env);
+ if (driver_target_new(&ctx, o.target, &o.target_features, tool, &target) !=
+ KIT_OK ||
+ driver_compiler_new(target, &ctx, &compiler) != KIT_OK) {
+ driver_errf(tool, "failed to initialize compiler");
+ rc = 1;
+ goto done;
+ }
+
+ build_fill_code(&o, &code);
+ diag.warnings_are_errors = o.warnings_are_errors ? true : false;
+ diag.max_errors = o.max_errors;
+
+ if (o.kind == BUILD_OUT_EXE) {
+ rc = build_run_link(&o, compiler, &ctx, &code, &diag, KIT_LINK_OUTPUT_EXE);
+ } else if (o.kind == BUILD_OUT_LIB) {
+ rc = o.shared ? build_run_link(&o, compiler, &ctx, &code, &diag,
+ KIT_LINK_OUTPUT_SHARED)
+ : build_run_archive(&o, compiler, &ctx, &code, &diag);
+ } else if (o.syntax_only) {
+ rc = build_run_per_source(&o, compiler, &ctx, &code, &diag);
+ } else if (o.emit == BUILD_EMIT_OBJ && o.nsources > 1) {
+ rc = build_run_relocatable(&o, compiler, &ctx, &code, &diag);
+ } else {
+ rc = build_run_per_source(&o, compiler, &ctx, &code, &diag);
+ }
+
+done:
+ if (compiler) driver_compiler_free(compiler);
+ kit_target_free(target);
+ if (runtime_resolved) driver_runtime_support_fini(&env, &runtime);
+ build_release(&o);
+ driver_env_fini(&env);
+ return rc;
+}
+
+/* ===================================================================== */
+/* help + entry points */
+/* ===================================================================== */
+
+void driver_help_build_exe(void) {
+ driver_printf(
+ "%.*s",
+ KIT_SLICE_ARG(KIT_SLICE_LIT(
+ "kit build-exe — link a polyglot source set into an executable\n"
+ "\n"
+ "USAGE\n"
+ " kit build-exe [options] inputs...\n"
+ "\n"
+ "DESCRIPTION\n"
+ " Compiles C / asm / toy / wasm sources (language per file) in\n"
+ " memory and links them with any .o/.a/.so inputs into one\n"
+ " executable. No intermediate files.\n"
+ "\n"
+ "OPTIONS (selection)\n"
+ " -o PATH Output (default a.out / a.exe)\n"
+ " -O0 -O1 -O2 -g Optimization / debug info\n"
+ " -target TRIPLE Cross-compile target\n"
+ " -static Fully static executable\n"
+ " -l NAME -L DIR Link a library / add a search dir\n"
+ " -e SYM -T script.ld Entry symbol / linker script\n"
+ " -Wl,... Linker pass-through\n"
+ " --group [flags] -- sources... Scope compile flags to sources\n"
+ " -X<lang> FLAG Per-language frontend flag (c|asm|toy|wasm)\n"
+ " -h, --help Show this help\n")));
+}
+
+void driver_help_build_lib(void) {
+ driver_printf(
+ "%.*s",
+ KIT_SLICE_ARG(KIT_SLICE_LIT(
+ "kit build-lib — build a static library (.a)\n"
+ "\n"
+ "USAGE\n"
+ " kit build-lib -o LIB.a [options] sources...\n"
+ "\n"
+ "DESCRIPTION\n"
+ " Compiles a polyglot source set in memory and archives the objects\n"
+ " into a static library (.a) with a symbol index. Dynamic/shared\n"
+ " libraries are not yet supported.\n"
+ "\n"
+ "OPTIONS (selection)\n"
+ " -o PATH Output archive (required)\n"
+ " -fPIC Position-independent code\n"
+ " -O0 -O1 -O2 -g Optimization / debug info\n"
+ " -target TRIPLE Cross-compile target\n"
+ " --group [flags] -- sources... Scope compile flags to sources\n"
+ " -X<lang> FLAG Per-language frontend flag (c|asm|toy|wasm)\n"
+ " -h, --help Show this help\n")));
+}
+
+void driver_help_build_obj(void) {
+ driver_printf(
+ "%.*s",
+ KIT_SLICE_ARG(KIT_SLICE_LIT(
+ "kit build-obj — compile sources to an object (or asm / C / IR)\n"
+ "\n"
+ "USAGE\n"
+ " kit build-obj [options] sources...\n"
+ "\n"
+ "DESCRIPTION\n"
+ " Compiles each source (C / asm / toy / wasm by suffix or -x) to an\n"
+ " object. Multiple sources with --emit=obj combine into one\n"
+ " relocatable object (ld -r). The kit-native replacement for the\n"
+ " retired `compile` tool.\n"
+ "\n"
+ "OPTIONS\n"
+ " -o PATH Output (default <base>.o; required for a\n"
+ " multi-source combine and for --emit=c)\n"
+ " --emit=obj|asm|c|ir Output form (ir requires -O1+)\n"
+ " -S Alias for --emit=asm\n"
+ " -fsyntax-only Check only; write no output\n"
+ " -O0 -O1 -O2 -g Optimization / debug info\n"
+ " -target TRIPLE Cross-compile target\n"
+ " -I/-isystem/-D/-U Preprocessor flags (C/asm frontends)\n"
+ " -x LANG Force language: c | asm | toy | wasm\n"
+ " --group [flags] -- sources... Scope compile flags to sources\n"
+ " -X<lang> FLAG Per-language frontend flag (c|asm|toy|wasm)\n"
+ " -o - Write the emit to stdout\n"
+ " -h, --help Show this help\n")));
+}
+
+int driver_build_exe(int argc, char** argv) {
+ if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
+ driver_help_build_exe();
+ return 0;
+ }
+ return build_main(argc, argv, BUILD_OUT_EXE, "build-exe");
+}
+
+int driver_build_lib(int argc, char** argv) {
+ if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
+ driver_help_build_lib();
+ return 0;
+ }
+ return build_main(argc, argv, BUILD_OUT_LIB, "build-lib");
+}
+
+int driver_build_obj(int argc, char** argv) {
+ if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
+ driver_help_build_obj();
+ return 0;
+ }
+ return build_main(argc, argv, BUILD_OUT_OBJ, "build-obj");
+}
diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c
@@ -13,6 +13,7 @@
#include "driver.h"
#include "hosted.h"
#include "lib_resolve.h"
+#include "link_engine.h"
#include "runtime.h"
/* `kit cc` — C compiler driver. With -c produces a single object;
@@ -2557,12 +2558,13 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o,
DriverLoad* dso_lf = NULL;
KitSlice* src_bytes = NULL;
KitSlice* obj_in = NULL;
+ KitSlice* obj_names = NULL;
KitLinkArchiveInput* arch_in = NULL;
KitSlice* dso_in = NULL;
+ KitSlice* dso_names = NULL;
KitLinkInputOrder* order = NULL;
KitObjBuilder** objs = NULL;
KitLinkScript* script = NULL;
- KitLinkSession* link = NULL;
KitCCompileOptions copts;
uint32_t nsrc = o->nsource_files + o->nsource_memory;
uint32_t i;
@@ -2591,7 +2593,8 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o,
if (o->nobject_files) {
obj_lf = driver_alloc_zeroed(env, o->nobject_files * sizeof(*obj_lf));
obj_in = driver_alloc_zeroed(env, o->nobject_files * sizeof(*obj_in));
- if (!obj_lf || !obj_in) {
+ obj_names = driver_alloc_zeroed(env, o->nobject_files * sizeof(*obj_names));
+ if (!obj_lf || !obj_in || !obj_names) {
driver_errf(CC_TOOL, "out of memory");
goto out;
}
@@ -2607,7 +2610,8 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o,
if (o->ndsos) {
dso_lf = driver_alloc_zeroed(env, o->ndsos * sizeof(*dso_lf));
dso_in = driver_alloc_zeroed(env, o->ndsos * sizeof(*dso_in));
- if (!dso_lf || !dso_in) {
+ dso_names = driver_alloc_zeroed(env, o->ndsos * sizeof(*dso_names));
+ if (!dso_lf || !dso_in || !dso_names) {
driver_errf(CC_TOOL, "out of memory");
goto out;
}
@@ -2630,6 +2634,7 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o,
if (driver_load_bytes(io, CC_TOOL, o->object_files[i], &obj_lf[i],
&obj_in[i]) != 0)
goto out;
+ obj_names[i] = kit_slice_cstr(o->object_files[i]);
}
for (i = 0; i < o->narchives; ++i) {
if (driver_load_bytes(io, CC_TOOL, o->archives[i].path, &arch_lf[i],
@@ -2643,6 +2648,7 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o,
if (driver_load_bytes(io, CC_TOOL, o->dsos[i].path, &dso_lf[i],
&dso_in[i]) != 0)
goto out;
+ dso_names[i] = kit_slice_cstr(o->dsos[i].path);
}
if (o->linker_script) {
@@ -2725,66 +2731,70 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o,
}
lopts.allow_undefined = 1;
- st = kit_link_session_new(compiler, &lopts, &link);
- if (order) {
+ /* Translate the command-line link order into the engine's public
+ * KitLinkInputOrder list. The dead-simple fallback for o->nlink_items == 0
+ * never fires (a link action always has at least one input), so every add
+ * flows through the ordered path. */
+ {
uint32_t oi;
- for (oi = 0; oi < o->nlink_items && st == KIT_OK; ++oi) {
+ DriverLinkInputs li;
+ for (oi = 0; oi < o->nlink_items; ++oi) {
const CcLinkItem* item = &o->link_items[oi];
+ KitLinkInputOrder* ord = &order[oi];
switch ((CcLinkItemKind)item->kind) {
case CC_LINK_SOURCE_FILE:
- st = kit_link_session_add_obj(link, objs[item->index]);
+ ord->kind = KIT_LINK_INPUT_OBJ;
+ ord->index = item->index;
break;
case CC_LINK_SOURCE_MEMORY:
- st = kit_link_session_add_obj(link,
- objs[o->nsource_files + item->index]);
+ ord->kind = KIT_LINK_INPUT_OBJ;
+ ord->index = o->nsource_files + item->index;
break;
case CC_LINK_OBJECT:
- st = kit_link_session_add_obj_bytes(
- link, kit_slice_cstr(o->object_files[item->index]),
- &obj_in[item->index]);
+ ord->kind = KIT_LINK_INPUT_OBJ_BYTES;
+ ord->index = item->index;
break;
case CC_LINK_ARCHIVE:
- st =
- kit_link_session_add_archive_bytes(link, &arch_in[item->index]);
+ ord->kind = KIT_LINK_INPUT_ARCHIVE;
+ ord->index = item->index;
break;
case CC_LINK_DSO:
- st = kit_link_session_add_dso_bytes(
- link, kit_slice_cstr(o->dsos[item->index].path),
- &dso_in[item->index]);
+ ord->kind = KIT_LINK_INPUT_DSO;
+ ord->index = item->index;
break;
case CC_LINK_LIB: {
const CcPendingLib* pl = &o->pending_libs[item->index];
if (pl->resolved_kind == CC_LINK_DSO) {
- st = kit_link_session_add_dso_bytes(
- link, kit_slice_cstr(o->dsos[pl->resolved_index].path),
- &dso_in[pl->resolved_index]);
+ ord->kind = KIT_LINK_INPUT_DSO;
+ ord->index = pl->resolved_index;
} else {
- st = kit_link_session_add_archive_bytes(
- link, &arch_in[pl->resolved_index]);
+ ord->kind = KIT_LINK_INPUT_ARCHIVE;
+ ord->index = pl->resolved_index;
}
break;
}
}
}
- } else {
- for (i = 0; i < nsrc && st == KIT_OK; ++i)
- st = kit_link_session_add_obj(link, objs[i]);
- for (i = 0; i < o->nobject_files && st == KIT_OK; ++i)
- st = kit_link_session_add_obj_bytes(
- link, kit_slice_cstr(o->object_files[i]), &obj_in[i]);
- for (i = 0; i < o->narchives && st == KIT_OK; ++i)
- st = kit_link_session_add_archive_bytes(link, &arch_in[i]);
- for (i = 0; i < o->ndsos && st == KIT_OK; ++i)
- st = kit_link_session_add_dso_bytes(
- link, kit_slice_cstr(o->dsos[i].path), &dso_in[i]);
- }
- if (st == KIT_OK) st = kit_link_session_emit(link, out_w);
+ memset(&li, 0, sizeof(li));
+ li.objs = objs;
+ li.nobjs = nsrc;
+ li.obj_names = obj_names;
+ li.obj_bytes = obj_in;
+ li.nobj_bytes = o->nobject_files;
+ li.archives = arch_in;
+ li.narchives = o->narchives;
+ li.dso_names = dso_names;
+ li.dso_bytes = dso_in;
+ li.ndsos = o->ndsos;
+ li.order = order;
+ li.norder = o->nlink_items;
+ st = driver_link_engine_emit(compiler, &lopts, &li, out_w);
+ }
rc = st == KIT_OK ? 0 : 1;
}
out:
if (out_w) kit_writer_close(out_w);
- kit_link_session_free(link);
if (rc == 0 && o->output_path) {
if (driver_mark_executable_output(o->output_path) != 0) {
driver_errf(CC_TOOL, "failed to set executable mode: %.*s",
@@ -2811,9 +2821,12 @@ out:
if (arch_in) driver_free(env, arch_in, o->narchives * sizeof(*arch_in));
if (arch_lf) driver_free(env, arch_lf, o->narchives * sizeof(*arch_lf));
if (dso_in) driver_free(env, dso_in, o->ndsos * sizeof(*dso_in));
+ if (dso_names) driver_free(env, dso_names, o->ndsos * sizeof(*dso_names));
if (dso_lf) driver_free(env, dso_lf, o->ndsos * sizeof(*dso_lf));
if (order) driver_free(env, order, o->nlink_items * sizeof(*order));
if (obj_in) driver_free(env, obj_in, o->nobject_files * sizeof(*obj_in));
+ if (obj_names)
+ driver_free(env, obj_names, o->nobject_files * sizeof(*obj_names));
if (obj_lf) driver_free(env, obj_lf, o->nobject_files * sizeof(*obj_lf));
if (src_lf) driver_free(env, src_lf, o->nsource_files * sizeof(*src_lf));
if (src_bytes)
diff --git a/driver/cmd/compile.c b/driver/cmd/compile.c
@@ -1,576 +0,0 @@
-#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;
- KitTargetSpec target;
- DriverTargetFeatures target_features;
-} 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;
- }
-
- {
- int tr = driver_target_features_try_consume(&o->target_features, o->env,
- COMPILE_TOOL, argc, argv, &i);
- if (tr < 0) return 1;
- if (tr > 0) 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;
- KitTarget* target = NULL;
- 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_target_features_init(&o.target_features, &env, argc) != 0) {
- driver_errf(COMPILE_TOOL, "out of memory");
- goto done;
- }
-
- 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_target_new(&ctx, o.target, &o.target_features, COMPILE_TOOL,
- &target) != KIT_OK ||
- driver_compiler_new(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);
- kit_target_free(target);
- if (runtime_resolved) driver_runtime_support_fini(&env, &runtime);
- driver_target_features_fini(&o.target_features, &env);
- driver_cflags_fini(&o.cf, &env);
- 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/driver.h b/driver/driver.h
@@ -38,7 +38,9 @@ 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_build_exe(int argc, char** argv);
+int driver_build_lib(int argc, char** argv);
+int driver_build_obj(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);
@@ -77,7 +79,9 @@ 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_build_exe(void);
+void driver_help_build_lib(void);
+void driver_help_build_obj(void);
void driver_help_install(void);
void driver_help_cpp(void);
void driver_help_as(void);
diff --git a/driver/lib/archive_engine.c b/driver/lib/archive_engine.c
@@ -0,0 +1,103 @@
+#include "archive_engine.h"
+
+#include <kit/archive.h>
+
+#include "inputs.h"
+
+int driver_archive_emit(DriverEnv* env, const KitContext* ctx, const char* tool,
+ KitObjBuilder* const* objs, const KitSlice* names,
+ uint32_t n, uint64_t epoch, KitWriter* out) {
+ KitArInput* members = NULL;
+ KitWriter** memw = NULL;
+ KitArMemberSymbols* msyms = NULL;
+ void** sym_blobs = NULL;
+ size_t* sym_blob_szs = NULL;
+ KitArWriteOptions opts = {0};
+ uint32_t i;
+ int rc = 1;
+
+ if (n == 0) {
+ driver_errf(tool, "no objects to archive");
+ return 1;
+ }
+
+ members = driver_alloc_zeroed(env, (size_t)n * sizeof(*members));
+ memw = driver_alloc_zeroed(env, (size_t)n * sizeof(*memw));
+ msyms = driver_alloc_zeroed(env, (size_t)n * sizeof(*msyms));
+ sym_blobs = driver_alloc_zeroed(env, (size_t)n * sizeof(*sym_blobs));
+ sym_blob_szs = driver_alloc_zeroed(env, (size_t)n * sizeof(*sym_blob_szs));
+ if (!members || !memw || !msyms || !sym_blobs || !sym_blob_szs) {
+ driver_errf(tool, "out of memory");
+ goto out;
+ }
+
+ /* Serialize each builder into its own in-memory writer; the member bytes
+ * alias that writer's buffer, so the writers stay open until kit_ar_write
+ * has consumed them. */
+ for (i = 0; i < n; ++i) {
+ const uint8_t* bytes;
+ size_t len = 0;
+ if (kit_writer_mem(ctx->heap, &memw[i]) != KIT_OK) {
+ driver_errf(tool, "out of memory");
+ goto out;
+ }
+ if (kit_obj_builder_emit(objs[i], memw[i]) != KIT_OK ||
+ kit_writer_status(memw[i]) != KIT_OK) {
+ driver_errf(tool, "failed to serialize object: %.*s",
+ KIT_SLICE_ARG(names[i]));
+ goto out;
+ }
+ bytes = kit_writer_mem_bytes(memw[i], &len);
+ members[i].name = names[i];
+ members[i].bytes.s = (const char*)bytes;
+ members[i].bytes.len = len;
+ }
+
+ /* Build the archive symbol index so the result links like a normal static
+ * library: collect each member's globally-defined symbols. */
+ for (i = 0; i < n; ++i) {
+ void* blob = NULL;
+ size_t blob_size = 0;
+ const KitSlice* syms = NULL;
+ uint32_t count = 0;
+ if (driver_collect_obj_global_syms(env, ctx, tool, &members[i].bytes, &blob,
+ &blob_size, &syms, &count) != 0)
+ goto out;
+ if (count == 0) continue;
+ sym_blobs[i] = blob;
+ sym_blob_szs[i] = blob_size;
+ msyms[i].names = syms;
+ msyms[i].count = count;
+ }
+
+ opts.epoch = epoch;
+ opts.long_names = 1;
+ opts.symbol_index = 1;
+ opts.member_symbols = msyms;
+
+ if (kit_ar_write(out, members, n, &opts) != KIT_OK ||
+ kit_writer_status(out) != KIT_OK) {
+ driver_errf(tool, "failed to write archive");
+ goto out;
+ }
+ rc = 0;
+
+out:
+ if (sym_blobs && sym_blob_szs) {
+ for (i = 0; i < n; ++i) {
+ if (sym_blobs[i])
+ driver_collect_obj_global_syms_free(env, sym_blobs[i], sym_blob_szs[i]);
+ }
+ }
+ if (memw) {
+ for (i = 0; i < n; ++i)
+ if (memw[i]) kit_writer_close(memw[i]);
+ }
+ if (members) driver_free(env, members, (size_t)n * sizeof(*members));
+ if (memw) driver_free(env, memw, (size_t)n * sizeof(*memw));
+ if (msyms) driver_free(env, msyms, (size_t)n * sizeof(*msyms));
+ if (sym_blobs) driver_free(env, sym_blobs, (size_t)n * sizeof(*sym_blobs));
+ if (sym_blob_szs)
+ driver_free(env, sym_blob_szs, (size_t)n * sizeof(*sym_blob_szs));
+ return rc;
+}
diff --git a/driver/lib/archive_engine.h b/driver/lib/archive_engine.h
@@ -0,0 +1,24 @@
+#ifndef KIT_DRIVER_ARCHIVE_ENGINE_H
+#define KIT_DRIVER_ARCHIVE_ENGINE_H
+
+#include <kit/core.h>
+#include <kit/object.h>
+
+#include "driver.h"
+
+/* Serialize each in-memory object builder to bytes and write them as the
+ * members of a POSIX `ar` archive (with a symbol index, the form a linker
+ * expects from a static library). objs[i] is emitted under member name
+ * names[i] (e.g. "a.o"); `epoch` seeds reproducible member timestamps.
+ *
+ * The builders are emitted, not freed — the caller keeps ownership. `out` is
+ * neither opened nor closed here. Returns 0 on success, 1 on failure (an error
+ * has already been reported via driver_errf with `tool`).
+ *
+ * Reused by `build-lib` (static archives) and available for a future `ar`
+ * pipeline that archives freshly compiled objects. */
+int driver_archive_emit(DriverEnv* env, const KitContext* ctx, const char* tool,
+ KitObjBuilder* const* objs, const KitSlice* names,
+ uint32_t n, uint64_t epoch, KitWriter* out);
+
+#endif
diff --git a/driver/lib/link_engine.c b/driver/lib/link_engine.c
@@ -0,0 +1,33 @@
+#include "link_engine.h"
+
+KitStatus driver_link_engine_emit(KitCompiler* compiler,
+ const KitLinkSessionOptions* lopts,
+ const DriverLinkInputs* in, KitWriter* out) {
+ KitLinkSession* link = NULL;
+ KitStatus st;
+ uint32_t i;
+
+ st = kit_link_session_new(compiler, lopts, &link);
+ for (i = 0; i < in->norder && st == KIT_OK; ++i) {
+ const KitLinkInputOrder* ord = &in->order[i];
+ switch ((KitLinkInputOrderKind)ord->kind) {
+ case KIT_LINK_INPUT_OBJ:
+ st = kit_link_session_add_obj(link, in->objs[ord->index]);
+ break;
+ case KIT_LINK_INPUT_OBJ_BYTES:
+ st = kit_link_session_add_obj_bytes(link, in->obj_names[ord->index],
+ &in->obj_bytes[ord->index]);
+ break;
+ case KIT_LINK_INPUT_ARCHIVE:
+ st = kit_link_session_add_archive_bytes(link, &in->archives[ord->index]);
+ break;
+ case KIT_LINK_INPUT_DSO:
+ st = kit_link_session_add_dso_bytes(link, in->dso_names[ord->index],
+ &in->dso_bytes[ord->index]);
+ break;
+ }
+ }
+ if (st == KIT_OK) st = kit_link_session_emit(link, out);
+ kit_link_session_free(link);
+ return st;
+}
diff --git a/driver/lib/link_engine.h b/driver/lib/link_engine.h
@@ -0,0 +1,44 @@
+#ifndef KIT_DRIVER_LINK_ENGINE_H
+#define KIT_DRIVER_LINK_ENGINE_H
+
+#include <kit/compile.h>
+#include <kit/link.h>
+#include <kit/object.h>
+
+/* Reusable "build a link session, add inputs in command-line order, and emit"
+ * step shared by `cc` and the `build-*` commands. Every input is already
+ * loaded/compiled by the caller; path lookup, option parsing, hosted/runtime
+ * wiring, and the output writer's lifetime stay caller responsibilities.
+ *
+ * `order` drives the add sequence: each entry's (kind, index) selects one
+ * element of the parallel arrays below, so link order matches the command
+ * line exactly.
+ * KIT_LINK_INPUT_OBJ -> objs[index] (in-memory builder; consumed)
+ * KIT_LINK_INPUT_OBJ_BYTES -> obj_bytes[index], labelled obj_names[index]
+ * KIT_LINK_INPUT_ARCHIVE -> archives[index]
+ * KIT_LINK_INPUT_DSO -> dso_bytes[index], labelled dso_names[index]
+ *
+ * Arrays whose count is zero may be NULL. */
+typedef struct DriverLinkInputs {
+ KitObjBuilder* const* objs;
+ uint32_t nobjs;
+ const KitSlice* obj_names;
+ const KitSlice* obj_bytes;
+ uint32_t nobj_bytes;
+ const KitLinkArchiveInput* archives;
+ uint32_t narchives;
+ const KitSlice* dso_names;
+ const KitSlice* dso_bytes;
+ uint32_t ndsos;
+ const KitLinkInputOrder* order;
+ uint32_t norder;
+} DriverLinkInputs;
+
+/* Open a session with `lopts`, add every input named by `in->order`, and emit
+ * to `out`. Returns the resulting KitStatus (KIT_OK on success). The session is
+ * always freed before return; `out` is neither opened nor closed here. */
+KitStatus driver_link_engine_emit(KitCompiler* compiler,
+ const KitLinkSessionOptions* lopts,
+ const DriverLinkInputs* in, KitWriter* out);
+
+#endif
diff --git a/driver/main.c b/driver/main.c
@@ -32,10 +32,20 @@ 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},
+#if KIT_TOOL_BUILD_EXE_ENABLED
+ {"build-exe", driver_build_exe, driver_help_build_exe,
+ "Compile a polyglot source set and link an executable (in memory)",
+ DRIVER_GROUP_TOOLCHAIN},
+#endif
+#if KIT_TOOL_BUILD_LIB_ENABLED
+ {"build-lib", driver_build_lib, driver_help_build_lib,
+ "Compile sources into a static .a or shared library (in memory)",
+ DRIVER_GROUP_TOOLCHAIN},
+#endif
+#if KIT_TOOL_BUILD_OBJ_ENABLED
+ {"build-obj", driver_build_obj, driver_help_build_obj,
+ "Compile sources to an object / asm / C / IR, or check (replaces compile)",
+ DRIVER_GROUP_TOOLCHAIN},
#endif
#if KIT_TOOL_INSTALL_ENABLED
{"install", driver_install, driver_help_install,
diff --git a/include/kit/config.h b/include/kit/config.h
@@ -104,7 +104,9 @@
* 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_BUILD_EXE_ENABLED 1
+#define KIT_TOOL_BUILD_LIB_ENABLED 1
+#define KIT_TOOL_BUILD_OBJ_ENABLED 1
#define KIT_TOOL_INSTALL_ENABLED 1
#define KIT_TOOL_CPP_ENABLED 1
#define KIT_TOOL_AS_ENABLED 1
diff --git a/mk/driver_srcs.mk b/mk/driver_srcs.mk
@@ -19,7 +19,9 @@ DRIVER_SRCS = driver/main.c driver/lib/target.c $(DRIVER_ENV_SRCS)
DRIVER_TOOL_SRCS = \
$(call tool-cmd,CC,cc) \
$(call tool-cmd,CHECK,cc) \
- $(call tool-cmd,COMPILE,compile) \
+ $(call tool-cmd,BUILD_EXE,build) \
+ $(call tool-cmd,BUILD_LIB,build) \
+ $(call tool-cmd,BUILD_OBJ,build) \
$(call tool-cmd,INSTALL,install) \
$(call tool-cmd,CPP,cpp) \
$(call tool-cmd,AS,as) \
@@ -50,13 +52,22 @@ DRIVER_SRCS += $(sort $(DRIVER_TOOL_SRCS))
# is enabled. The cas/pkg tools link libkit's public cas/package APIs (gated
# by KIT_CAS_ENABLED / KIT_PKG_ENABLED, asserted by config_assert.c); the dist
# implementation and vendored primitives live in the library now.
-DRIVER_SRCS += $(call need-any,CC CHECK CPP AS DBG RUN,driver/lib/cflags.c)
-DRIVER_SRCS += $(call need-any,CC CHECK LD RUN,driver/lib/lib_resolve.c)
-DRIVER_SRCS += $(call need-any,CC CHECK RUN,driver/lib/hosted.c)
-DRIVER_SRCS += $(call need-any,CC CHECK LD,driver/lib/runtime.c)
-DRIVER_SRCS += $(call need-any,AR RANLIB STRIP DBG RUN,driver/lib/inputs.c)
+# build.c is one shared TU compiled whenever ANY of BUILD_EXE/LIB/OBJ is on, and
+# it references driver_link_engine_emit, driver_archive_emit, and
+# driver_lib_resolve_for_os unconditionally (the call sites are gated at runtime,
+# not by #if). So link_engine/archive_engine/lib_resolve — and inputs, which
+# archive_engine pulls in — must be present for every BUILD_* subset, not just
+# the tools that exercise each path. Hence the full BUILD_EXE BUILD_LIB BUILD_OBJ
+# union below.
+DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ CPP AS DBG RUN,driver/lib/cflags.c)
+DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ LD RUN,driver/lib/lib_resolve.c)
+DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ RUN,driver/lib/hosted.c)
+DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ LD,driver/lib/runtime.c)
+DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/link_engine.c)
+DRIVER_SRCS += $(call need-any,BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/archive_engine.c)
+DRIVER_SRCS += $(call need-any,AR RANLIB STRIP DBG RUN BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/inputs.c)
DRIVER_SRCS += $(call need-any,RUN,driver/lib/wasm_run.c)
-DRIVER_SRCS += $(call need-any,CC CHECK COMPILE,driver/lib/compile_engine.c)
+DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/compile_engine.c)
DRIVER_SRCS += $(call need-any,CAS PKG,driver/lib/dist_host.c)
DRIVER_SRCS := $(sort $(DRIVER_SRCS))
diff --git a/mk/test.mk b/mk/test.mk
@@ -66,7 +66,7 @@ TEST_TARGETS = \
test-driver-ar \
test-driver-cas \
test-driver-cc \
- test-driver-compile \
+ test-driver-build \
test-driver-objcopy \
test-driver-objdump \
test-driver-pkg \
@@ -193,13 +193,13 @@ test-images:
test-cf-corpus-selftest:
@bash test/lib/kit_corpus_selftest.sh
-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-wasm
+test-driver: test-driver-cc test-driver-build 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-wasm
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-driver-build: bin
+ @KIT=$(abspath $(BIN)) sh test/buildcmds/run.sh
# test-cbackend: --emit=c C-source backend, driven through three
# frontends — parse-runner (C), toy-runner (toy), wasm-runner (wat/wasm).
diff --git a/test/buildcmds/run.sh b/test/buildcmds/run.sh
@@ -0,0 +1,180 @@
+#!/bin/sh
+# Driver-level checks for the kit-native build verbs `build-exe` / `build-lib` /
+# `build-obj` (the trio that replaced `compile`). Self-checking (no golden
+# files): we assert exit status, output existence, symbol/text markers, ld -r
+# parity, and — for build-exe — native execution of the produced binary.
+#
+# Coverage:
+# build-obj : per-language compile (C / toy / wasm), --emit=obj|asm|c|ir,
+# -fsyntax-only, default output naming, -o - to stdout, the
+# -X<lang> frontend-flag router, target features, the multi-source
+# relocatable combine (with ld -r parity), and the negative paths.
+# build-lib : static .a from mixed sources, then link against it.
+# build-exe : polyglot relocatable combine, native link+exec, --group scoping
+# (verified through the produced exit code), and -L/-l.
+
+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 "build: kit binary not found at $KIT" >&2
+ exit 2
+fi
+
+work=$(mktemp -d "${TMPDIR:-/tmp}/kit-build-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 helper(void);\nint main(void){return ZERO+helper();}\n' > "$work/hello.c"
+printf 'int helper(void){return 0;}\n' > "$work/helper.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"
+
+# ===========================================================================
+# build-obj — the compile replacement
+# ===========================================================================
+
+# ---- C: preprocessor flags + object output ---------------------------------
+run_ok bo-c-obj "$KIT" build-obj -c -Iinc -DUNUSED=1 hello.c -o hello.o
+assert_file_exists bo-c-obj-exists hello.o
+"$KIT" nm hello.o > nm.out 2>/dev/null
+contains bo-c-obj-has-main nm.out main
+
+# C freestanding system header (<stdint.h>) resolves via the rt include set.
+run_ok bo-c-freestanding-header "$KIT" build-obj -c std.c -o std.o
+
+# Default output name: <basename>.o next to cwd when -o is omitted.
+run_ok bo-c-default-name "$KIT" build-obj -c -Iinc hello.c
+assert_file_exists bo-c-default-name-file hello.o
+
+# ---- toy + wasm: non-C frontends through the generic driver -----------------
+run_ok bo-toy "$KIT" build-obj -c prog.toy -o prog_toy.o
+assert_file_exists bo-toy-exists prog_toy.o
+run_ok bo-wasm "$KIT" build-obj -c prog.wat -o prog_wat.o
+assert_file_exists bo-wasm-exists prog_wat.o
+
+# Target-owned option: the driver lowers feature flags into KitTargetOptions.
+run_ok bo-wasm-target-feature "$KIT" build-obj -target wasm32-none -c \
+ -mattr=-tail-calls prog.wat -o prog_wat2.o
+
+# Per-language frontend flag via -X<lang> (routed to the wasm frontend parser).
+run_ok bo-Xwasm "$KIT" build-obj -target wasm32-none -c \
+ -Xwasm -mno-feature=tail-calls prog.wat -o prog_wat3.o
+
+# ---- emit modes ------------------------------------------------------------
+run_ok bo-emit-asm "$KIT" build-obj -S -Iinc hello.c -o hello.s
+contains bo-emit-asm-text hello.s .text
+run_ok bo-emit-asm-long "$KIT" build-obj --emit=asm -Iinc hello.c -o hello2.s
+run_ok bo-emit-c "$KIT" build-obj --emit=c prog.toy -o prog.c
+assert_file_exists bo-emit-c-file prog.c
+run_ok bo-emit-ir "$KIT" build-obj --emit=ir -O1 prog.toy -o prog.ir
+assert_file_exists bo-emit-ir-file prog.ir
+
+# -o - sends the emit to stdout (reusing the cc stdout writer).
+"$KIT" build-obj --emit=ir -O1 prog.toy -o - > ir.stdout 2>/dev/null
+if [ -s ir.stdout ]; then ok bo-emit-ir-stdout; else not_ok bo-emit-ir-stdout; fi
+
+# ---- check-only writes no object -------------------------------------------
+rm -f hello.o
+run_ok bo-check-only "$KIT" build-obj -fsyntax-only -Iinc hello.c helper.c
+if [ ! -f hello.o ]; then ok bo-check-no-output; else not_ok bo-check-no-output; fi
+
+# ---- multi-source relocatable combine (ld -r) ------------------------------
+run_ok bo-reloc-combine "$KIT" build-obj -Iinc hello.c helper.c -o combined.o
+assert_file_exists bo-reloc-combine-file combined.o
+"$KIT" nm combined.o > combined.nm 2>/dev/null
+contains bo-reloc-has-helper combined.nm helper
+contains bo-reloc-has-main combined.nm main
+
+# ld -r parity: the in-memory combine matches a separate compile + `ld -r`.
+"$KIT" build-obj -c -Iinc hello.c -o sep_hello.o 2>/dev/null
+"$KIT" build-obj -c helper.c -o sep_helper.o 2>/dev/null
+"$KIT" ld -r sep_hello.o sep_helper.o -o ldr.o 2>/dev/null
+"$KIT" nm combined.o | sort > combine.sorted 2>/dev/null
+"$KIT" nm ldr.o | sort > ldr.sorted 2>/dev/null
+same_file bo-reloc-ld-r-parity ldr.sorted combine.sorted
+
+# Polyglot combine: C + toy in one relocatable object.
+run_ok bo-polyglot-combine "$KIT" build-obj -Iinc helper.c prog.toy -o poly.o
+
+# ---- negative paths --------------------------------------------------------
+# A frontend with an unknown -X flag is rejected by that frontend's parser.
+run_fail bo-neg-bad-Xflag "$KIT" build-obj -c -Xtoy --bogus prog.toy -o x.o
+# The target parser rejects an unknown feature name.
+run_fail bo-neg-bad-feature "$KIT" build-obj -target wasm32-none \
+ -mattr=+nope -c prog.wat -o x.o
+# build-obj never links: object/archive inputs are refused.
+run_fail bo-neg-link-input "$KIT" build-obj hello.o
+# --emit=c needs an explicit destination.
+run_fail bo-neg-emit-c-needs-o "$KIT" build-obj --emit=c prog.toy
+# IR is only available with the optimizer on.
+run_fail bo-neg-ir-needs-opt "$KIT" build-obj --emit=ir prog.toy -o x.ir
+# -o cannot fan out across multiple sources in a per-source emit.
+run_fail bo-neg-o-multi-asm "$KIT" build-obj -S -Iinc hello.c std.c -o both.s
+# A per-output flag is not allowed inside a --group.
+run_fail bo-neg-global-in-group "$KIT" build-obj -Iinc --group -O2 -- hello.c
+# -dynamic is a build-lib concept.
+run_fail bo-neg-dynamic "$KIT" build-obj -dynamic prog.toy
+# unknown flag.
+run_fail bo-neg-unknown-flag "$KIT" build-obj --nope -c prog.toy -o x.o
+
+# ===========================================================================
+# build-lib — static archive
+# ===========================================================================
+
+run_ok bl-static "$KIT" build-lib -Iinc -o libmix.a helper.c std.c prog.toy
+assert_file_exists bl-static-file libmix.a
+"$KIT" nm libmix.a > libmix.nm 2>/dev/null
+contains bl-static-has-helper libmix.nm helper
+# -o is required (no obvious base name across N sources).
+run_fail bl-neg-needs-o "$KIT" build-lib helper.c std.c
+# build-lib takes only sources.
+run_fail bl-neg-link-input "$KIT" build-lib -o x.a helper.o
+# Dynamic/shared libraries are not yet supported.
+run_fail bl-neg-dynamic "$KIT" build-lib -dynamic -o x.a helper.c
+
+# ===========================================================================
+# build-exe — native link + exec (the headline path)
+# ===========================================================================
+
+# A hosted executable (-lc) that returns 0; build, then run it.
+run_ok be-link "$KIT" build-exe -lc -Iinc hello.c helper.c -o app
+is_executable be-link-exec app
+run_ok be-run ./app
+
+# Default output name a.out when -o is omitted.
+run_ok be-default-name "$KIT" build-exe -lc -Iinc hello.c helper.c
+assert_file_exists be-default-name-file a.out
+
+# --group scoping is observable through the program's exit code: the bare TU
+# sees -DRET=0 (global), the grouped TU overrides it to a non-zero value.
+printf '#ifndef RET\n#define RET 7\n#endif\nint ret_val(void){return RET;}\n' > rv.c
+printf 'int ret_val(void);\nint main(void){return ret_val();}\n' > rmain.c
+run_ok be-group-link "$KIT" build-exe -lc -DRET=0 rmain.c \
+ --group -DRET=3 -- rv.c -o appg
+if ./appg; then code=0; else code=$?; fi
+if [ "$code" -eq 3 ]; then ok be-group-scope; else
+ echo "expected exit 3 from grouped -DRET, got $code" > "$work/be-group-scope.diag"
+ not_ok be-group-scope "$work/be-group-scope.diag"
+fi
+
+# Link against the static library built above via -L/-l, then run.
+printf 'int helper(void);\nint main(void){return helper();}\n' > usemix.c
+run_ok be-link-archive "$KIT" build-exe -lc usemix.c -L. -lmix -o app2
+run_ok be-run-archive ./app2
+
+kit_summary build-driver
+kit_exit
diff --git a/test/compile/run.sh b/test/compile/run.sh
@@ -1,98 +0,0 @@
-#!/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 target
-# feature flag path, and the negative paths that exercise the capability gate,
-# the target/frontend option parsers, 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
-
-# Target-owned option: the driver lowers feature flags into KitTargetOptions.
-run_ok wasm-feature-flag "$KIT" compile -target wasm32-none -c \
- -mattr=-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 target parser rejects an unknown feature name.
-run_fail neg-bad-target-feature "$KIT" compile -target wasm32-none \
- -mattr=+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