kit

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

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:
Mdoc/DRIVER.md | 35+++++++++++++++++++++++++----------
Mdoc/plan/README.md | 1+
Adoc/plan/TODO.md | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adriver/cmd/build.c | 2333+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/cmd/cc.c | 85+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Ddriver/cmd/compile.c | 576-------------------------------------------------------------------------------
Mdriver/driver.h | 8++++++--
Adriver/lib/archive_engine.c | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adriver/lib/archive_engine.h | 24++++++++++++++++++++++++
Adriver/lib/link_engine.c | 33+++++++++++++++++++++++++++++++++
Adriver/lib/link_engine.h | 44++++++++++++++++++++++++++++++++++++++++++++
Mdriver/main.c | 18++++++++++++++----
Minclude/kit/config.h | 4+++-
Mmk/driver_srcs.mk | 25++++++++++++++++++-------
Mmk/test.mk | 8++++----
Atest/buildcmds/run.sh | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dtest/compile/run.sh | 98-------------------------------------------------------------------------------
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