kit

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

commit a89695e3122b7a118a91d8828d350e2ffc18409c
parent 58a41780a77ed786a88361881b40d8059cc29a94
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu,  4 Jun 2026 08:43:13 -0700

Drain driver TODO backlog

Diffstat:
Mdoc/plan/README.md | 2+-
Mdoc/plan/TODO.md | 80++++---------------------------------------------------------------------------
Mdriver/cmd/build.c | 359++++++++++---------------------------------------------------------------------
Mdriver/cmd/cc.c | 359++++++++-----------------------------------------------------------------------
Mdriver/lib/link_engine.h | 6++++--
Adriver/lib/link_flags.c | 441+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adriver/lib/link_flags.h | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmk/driver_srcs.mk | 1+
Mmk/test.mk | 12+++++++++++-
Mtest/buildcmds/run.sh | 10++++++++++
10 files changed, 607 insertions(+), 719 deletions(-)

diff --git a/doc/plan/README.md b/doc/plan/README.md @@ -18,4 +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. | — | +| [TODO.md](TODO.md) | Open deferred fixes and code smells only. Completed items are removed instead of checked off. Not a roadmap; a current backlog. | — | diff --git a/doc/plan/TODO.md b/doc/plan/TODO.md @@ -1,80 +1,8 @@ # 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. +No open items. -## Memory / correctness (driver) +This file is an open-backlog catalog, not a completion ledger. When an item is +fixed, remove it instead of checking it off or keeping a closed entry. -### 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. +Add new deferred fixes below as they are discovered. diff --git a/driver/cmd/build.c b/driver/cmd/build.c @@ -12,6 +12,7 @@ #include "hosted.h" #include "lib_resolve.h" #include "link_engine.h" +#include "link_flags.h" #include "runtime.h" /* `kit build-exe` / `build-lib` / `build-obj` — the kit-native build verbs. @@ -140,11 +141,10 @@ typedef struct BuildOptions { int opt_level; int debug_info; int dynamic; /* -dynamic / -shared */ + int shared_requested; /* -shared spelling, for build-exe diagnostics */ 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 */ @@ -155,18 +155,8 @@ typedef struct BuildOptions { 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; + /* Link-session options and owned -Wl state. */ + DriverLinkFlags link; uint64_t epoch; /* Hosted libc / sysroot state. */ @@ -229,80 +219,6 @@ static int build_parse_u64(const char* s, uint64_t* out) { 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; @@ -321,157 +237,6 @@ static int build_record_mcmodel(BuildOptions* o, const char* 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; @@ -517,18 +282,16 @@ static int build_alloc(BuildOptions* o, int argc) { 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) { + !o->dsos || !o->lib_search_paths || !o->pending_libs || !o->link_items) { 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 || + if (driver_link_flags_init(&o->link, o->env, o->tool, (uint32_t)bound) != 0 || + 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; @@ -554,11 +317,10 @@ static void build_release(BuildOptions* o) { 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_link_flags_fini(&o->link); 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)); @@ -573,7 +335,6 @@ static void build_release(BuildOptions* o) { 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)); } /* ===================================================================== */ @@ -735,6 +496,10 @@ static int build_try_scopable(BuildOptions* o, BuildGroup* g, int argc, driver_errf(o->tool, "-x requires an argument"); return -1; } + if (driver_streq(argv[*i], "--")) { + driver_errf(o->tool, "-x requires an argument before `--`"); + 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]))); @@ -756,6 +521,11 @@ static int build_try_scopable(BuildOptions* o, BuildGroup* g, int argc, KIT_SLICE_ARG(kit_slice_cstr(a))); return -1; } + if (driver_streq(argv[*i], "--")) { + driver_errf(o->tool, "%.*s requires a following flag before `--`", + KIT_SLICE_ARG(kit_slice_cstr(a))); + return -1; + } f = &g->fe[g->nfe++]; f->lang = (uint8_t)lang; f->tok = argv[*i]; @@ -1003,8 +773,8 @@ static int build_parse(int argc, char** argv, BuildOptions* o) { continue; } if (driver_streq(a, "-shared")) { - /* Hidden alias for -dynamic on build-lib; rejected elsewhere. */ o->dynamic = 1; + o->shared_requested = 1; continue; } if (driver_streq(a, "-pie")) { @@ -1052,7 +822,7 @@ static int build_parse(int argc, char** argv, BuildOptions* o) { driver_errf(o->tool, "-e requires an argument"); return 1; } - o->entry = argv[i]; + o->link.entry = argv[i]; continue; } if (driver_streq(a, "-T")) { @@ -1060,11 +830,11 @@ static int build_parse(int argc, char** argv, BuildOptions* o) { driver_errf(o->tool, "-T requires an argument"); return 1; } - o->linker_script = argv[i]; + o->link.linker_script = argv[i]; continue; } if (driver_strneq(a, "-Wl,", 4)) { - if (build_record_wl(o, a + 4) != 0) return 1; + if (driver_link_flags_record_wl(&o->link, a + 4) != 0) return 1; continue; } if (driver_streq(a, "-Xlinker")) { @@ -1072,19 +842,19 @@ static int build_parse(int argc, char** argv, BuildOptions* o) { driver_errf(o->tool, "-Xlinker requires an argument"); return 1; } - if (build_record_wl(o, argv[i]) != 0) return 1; + if (driver_link_flags_record_wl(&o->link, argv[i]) != 0) return 1; continue; } if (driver_strneq(a, "--build-id=", 11)) { - if (build_record_build_id(o, a + 11) != 0) return 1; + if (driver_link_flags_record_build_id(&o->link, a + 11) != 0) return 1; continue; } if (driver_streq(a, "-mwindows")) { - o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI; + o->link.pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI; continue; } if (driver_streq(a, "-mconsole")) { - o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI; + o->link.pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI; continue; } if (driver_strneq(a, "-mcmodel=", 9)) { @@ -1269,8 +1039,8 @@ static int build_apply_hosted_profile(BuildOptions* o) { 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; + if (!o->link.interp_path && o->hosted.interp_path) + o->link.interp_path = o->hosted.interp_path; return 0; } @@ -1567,54 +1337,6 @@ static int build_open_output(const KitContext* ctx, DriverEnv* env, 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, @@ -1699,10 +1421,10 @@ static int build_run_link(BuildOptions* o, KitCompiler* compiler, goto out; dso_names[i] = kit_slice_cstr(o->dsos[i].path); } - if (o->linker_script) { + if (o->link.linker_script) { KitSlice dummy; - if (driver_load_bytes(io, o->tool, o->linker_script, &script_lf, &dummy) != - 0) + if (driver_load_bytes(io, o->tool, o->link.linker_script, &script_lf, + &dummy) != 0) goto out; } @@ -1756,8 +1478,10 @@ static int build_run_link(BuildOptions* o, KitCompiler* compiler, KitLinkSessionOptions lopts; DriverLinkInputs li; KitStatus st; - if (build_fill_link_opts(o, env, script, output_kind, &lopts, - &rpath_slices) != 0) + if (driver_link_flags_fill_options( + &o->link, o->target, o->pie, o->shared, + output_kind == KIT_LINK_OUTPUT_RELOCATABLE, output_kind, script, + &lopts, &rpath_slices) != 0) goto out; memset(&li, 0, sizeof(li)); li.objs = objs; @@ -1787,8 +1511,7 @@ out: } } if (script) kit_link_script_free(ctx, script); - if (rpath_slices) - driver_free(env, rpath_slices, o->nrpaths * sizeof(*rpath_slices)); + driver_link_flags_free_rpath_slices(&o->link, 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]); @@ -1851,7 +1574,7 @@ static int build_run_relocatable(BuildOptions* o, KitCompiler* compiler, memset(&lopts, 0, sizeof(lopts)); lopts.output_kind = KIT_LINK_OUTPUT_RELOCATABLE; lopts.allow_undefined = 1; - lopts.strip_debug = o->strip_debug ? true : false; + lopts.strip_debug = o->link.strip_debug ? true : false; memset(&li, 0, sizeof(li)); li.objs = objs; li.nobjs = o->nsources; @@ -1991,11 +1714,15 @@ static int build_validate(BuildOptions* o) { 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). */ + /* -dynamic selects the default executable link mode for build-exe and the + * not-yet-supported shared-library mode for build-lib. -shared is the GCC + * spelling for producing a shared library, so build-exe rejects it. */ if (o->kind == BUILD_OUT_EXE) { + if (o->shared_requested) { + driver_errf(o->tool, "-shared is not valid for build-exe"); + 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; diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c @@ -14,6 +14,7 @@ #include "hosted.h" #include "lib_resolve.h" #include "link_engine.h" +#include "link_flags.h" #include "runtime.h" /* `kit cc` — C compiler driver. With -c produces a single object; @@ -133,9 +134,6 @@ typedef struct CcOptions { int output_path_set; char* owned_output_path; size_t owned_output_path_size; - const char* entry; /* -e */ - const char* linker_script; /* -T path */ - uint16_t pe_subsystem; /* KitPeSubsystem */ const char* sysroot; /* --sysroot / -isysroot */ int freestanding; /* -ffreestanding (suppresses sysroot headers) */ uint8_t default_visibility; /* KitSymVis; -fvisibility=... */ @@ -151,12 +149,6 @@ typedef struct CcOptions { char** owned_path_map_olds; size_t* owned_path_map_old_sizes; - /* Build-id. mode is a KitBuildIdMode value; bytes/len are owned and - * non-NULL only when mode == KIT_BUILDID_USER. */ - uint8_t build_id_mode; - uint8_t* build_id_bytes; - uint32_t build_id_len; - /* Reproducibility: SOURCE_DATE_EPOCH parsed at end-of-parse. */ uint64_t epoch; @@ -203,23 +195,15 @@ typedef struct CcOptions { const char** dep_targets; /* -MT/-MQ */ uint32_t ndep_targets; - /* Shared-library link state — reachable via -shared and -Wl,... pass- - * throughs. cc forwards these to kit_link_shared in the same way GCC - * forwards -Wl,-soname=... to ld. rpaths/runpaths alias argv. */ - int shared; /* -shared */ - const char* soname; /* -Wl,-soname=NAME */ - const char** rpaths; /* -Wl,-rpath=DIR (repeatable) */ - uint32_t nrpaths; - int new_dtags; /* 1=DT_RUNPATH (default), 0=DT_RPATH */ + /* Link-session options and owned -Wl state. */ + DriverLinkFlags link; + int shared; /* -shared */ int static_link; int pie; - int gc_sections; - const char* interp_path; int no_stdlib; int no_defaultlibs; int no_startfiles; int wants_hosted_libc; - int strip_debug; DriverHostedPlan hosted; } CcOptions; @@ -307,17 +291,16 @@ static int cc_alloc_arrays(CcOptions* o, int argc) { driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_path_map_olds)); o->owned_path_map_old_sizes = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_path_map_old_sizes)); - o->rpaths = driver_alloc_zeroed(o->env, bound * sizeof(*o->rpaths)); if (!o->source_files || !o->source_langs || !o->source_memory || !o->object_files || !o->archives || !o->dsos || !o->lib_search_paths || !o->pending_libs || !o->link_items || !o->dep_targets || !o->path_map || - !o->owned_path_map_olds || !o->owned_path_map_old_sizes || !o->rpaths) { + !o->owned_path_map_olds || !o->owned_path_map_old_sizes) { driver_errf(CC_TOOL, "out of memory"); return 1; } - o->new_dtags = 1; o->cur_link_mode = KIT_LM_DEFAULT; - if (driver_cflags_init(&o->cf, o->env, + if (driver_link_flags_init(&o->link, o->env, CC_TOOL, (uint32_t)bound) != 0 || + driver_cflags_init(&o->cf, o->env, (int)(bound + DRIVER_HOSTED_MAX_DEFINES + DRIVER_HOSTED_MAX_INCLUDES)) != 0 || driver_target_features_init(&o->target_features, o->env, argc) != 0) { @@ -348,14 +331,13 @@ static void cc_options_release(CcOptions* o) { } } if (o->stdin_buf) driver_free(o->env, o->stdin_buf, o->stdin_size); - if (o->build_id_bytes) - driver_free(o->env, o->build_id_bytes, o->build_id_len); if (o->owned_output_path) driver_free(o->env, o->owned_output_path, o->owned_output_path_size); 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_link_flags_fini(&o->link); driver_target_features_fini(&o->target_features, o->env); driver_cflags_fini(&o->cf, o->env); driver_free(o->env, o->source_files, bound * sizeof(*o->source_files)); @@ -374,190 +356,10 @@ static void cc_options_release(CcOptions* o) { bound * sizeof(*o->owned_path_map_olds)); driver_free(o->env, o->owned_path_map_old_sizes, bound * sizeof(*o->owned_path_map_old_sizes)); - driver_free(o->env, o->rpaths, bound * sizeof(*o->rpaths)); -} - -static char* cc_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 cc_record_build_id(CcOptions* o, const char* val); static int cc_apply_hosted_profile(CcOptions* o); -static int cc_subsystem_value_eq(const char* val, size_t n, const char* want) { - size_t i; - for (i = 0; want[i]; ++i) { - char a; - char 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 cc_record_pe_subsystem(CcOptions* o, const char* val, size_t n) { - if (cc_subsystem_value_eq(val, n, "CONSOLE") || - cc_subsystem_value_eq(val, n, "CUI")) { - o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI; - return 0; - } - if (cc_subsystem_value_eq(val, n, "WINDOWS") || - cc_subsystem_value_eq(val, n, "GUI")) { - o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI; - return 0; - } - driver_errf(CC_TOOL, "unsupported subsystem: %.*s", (int)n, val); - return 1; -} - -/* Parse a single GCC-style -Wl,X[,Y...] pass-through argument. */ -static int cc_record_wl(CcOptions* o, const char* arg) { - const char* p = arg; - int expect_rpath = 0; - int expect_soname = 0; - int expect_interp = 0; - int expect_subsystem = 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_subsystem) { - char* buf = cc_dup_span(o->env, tok, n); - if (!buf) { - driver_errf(CC_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_subsystem) { - int rc = cc_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_subsystem = 0; - continue; - } - - if (n >= 8 && driver_strneq(tok, "-soname=", 8)) { - char* buf; - size_t bufsz = n - 8 + 1; - buf = driver_alloc(o->env, bufsz); - if (!buf) { - driver_errf(CC_TOOL, "out of memory"); - return 1; - } - driver_memcpy(buf, tok + 8, n - 8); - buf[n - 8] = '\0'; - o->soname = buf; - continue; - } - if (n == 7 && driver_strneq(tok, "-soname", 7)) { - expect_soname = 1; - continue; - } - if (n >= 7 && driver_strneq(tok, "-rpath=", 7)) { - char* buf; - size_t bufsz = n - 7 + 1; - buf = driver_alloc(o->env, bufsz); - if (!buf) { - driver_errf(CC_TOOL, "out of memory"); - return 1; - } - driver_memcpy(buf, tok + 7, n - 7); - buf[n - 7] = '\0'; - 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 = cc_dup_span(o->env, tok + 16, n - 16); - if (!o->interp_path) { - driver_errf(CC_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)) { - o->strip_debug = 1; - continue; - } - if (n == 13 && driver_strneq(tok, "--strip-debug", 13)) { - o->strip_debug = 1; - continue; - } - if (n >= 11 && driver_strneq(tok, "--build-id=", 11)) { - char* buf = cc_dup_span(o->env, tok + 11, n - 11); - int rc; - if (!buf) { - driver_errf(CC_TOOL, "out of memory"); - return 1; - } - rc = cc_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 (cc_record_pe_subsystem(o, tok + 12, n - 12) != 0) return 1; - continue; - } - if (n == 11 && driver_strneq(tok, "--subsystem", 11)) { - expect_subsystem = 1; - continue; - } - if (n >= 11 && driver_strneq(tok, "/SUBSYSTEM:", 11)) { - if (cc_record_pe_subsystem(o, tok + 11, n - 11) != 0) return 1; - continue; - } - - driver_errf(CC_TOOL, "unsupported -Wl, token: %.*s", (int)n, tok); - return 1; - } - if (expect_rpath || expect_soname || expect_interp || expect_subsystem) { - driver_errf(CC_TOOL, "-Wl option requires another comma argument"); - return 1; - } - return 0; -} - static int cc_is_c_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") || @@ -657,44 +459,6 @@ static int cc_parse_u64(const char* s, uint64_t* out) { return 0; } -static int cc_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 cc_record_path_map(CcOptions* o, const char* arg) { const char* eq = driver_strchr(arg, '='); KitPathPrefixMap* m = &o->path_map[o->npath_map]; @@ -721,34 +485,6 @@ static int cc_record_path_map(CcOptions* o, const char* arg) { return 0; } -static int cc_record_build_id(CcOptions* 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 (cc_parse_hex_bytes(o->env, val + 2, &o->build_id_bytes, - &o->build_id_len) != 0) { - driver_errf(CC_TOOL, - "--build-id=0x... requires an even-length hex string"); - return 1; - } - o->build_id_mode = KIT_BUILDID_USER; - return 0; - } - driver_errf(CC_TOOL, "unknown --build-id value: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(val))); - return 1; -} - static int cc_record_mcmodel(CcOptions* o, const char* val) { if (driver_streq(val, "small")) { o->target.code_model = KIT_CM_SMALL; @@ -1077,8 +813,8 @@ static int cc_apply_hosted_profile(CcOptions* o) { if (o->no_startfiles) break; if (cc_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; + if (!o->link.interp_path && o->hosted.interp_path) + o->link.interp_path = o->hosted.interp_path; return 0; } @@ -1492,15 +1228,15 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { continue; } if (driver_streq(a, "-mwindows")) { - o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI; + o->link.pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI; continue; } if (driver_streq(a, "-mconsole")) { - o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI; + o->link.pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI; continue; } if (driver_strneq(a, "-Wl,", 4)) { - if (cc_record_wl(o, a + 4) != 0) return 1; + if (driver_link_flags_record_wl(&o->link, a + 4) != 0) return 1; continue; } @@ -1515,7 +1251,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { if (tr > 0) continue; } if (driver_strneq(a, "--build-id=", 11)) { - if (cc_record_build_id(o, a + 11) != 0) return 1; + if (driver_link_flags_record_build_id(&o->link, a + 11) != 0) return 1; continue; } if (driver_strneq(a, "-ffile-prefix-map=", 18)) { @@ -1551,7 +1287,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { driver_errf(CC_TOOL, "-e requires an argument"); return 1; } - o->entry = argv[i]; + o->link.entry = argv[i]; continue; } if (driver_streq(a, "-T")) { @@ -1559,7 +1295,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { driver_errf(CC_TOOL, "-T requires an argument"); return 1; } - o->linker_script = argv[i]; + o->link.linker_script = argv[i]; continue; } if (driver_streq(a, "-target")) { @@ -1602,7 +1338,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { driver_errf(CC_TOOL, "-Xlinker requires an argument"); return 1; } - if (cc_record_wl(o, argv[i]) != 0) return 1; + if (driver_link_flags_record_wl(&o->link, argv[i]) != 0) return 1; continue; } if (driver_streq(a, "-x")) { @@ -1774,7 +1510,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { return 1; } } - if (!o->shared && o->soname) { + if (!o->shared && o->link.soname) { driver_errf(CC_TOOL, "-Wl,-soname requires -shared"); return 1; } @@ -2540,10 +2276,10 @@ out: return rc; } -/* exe path: compile every C source via a single KitCompiler, load - * .o/.a/script inputs, and call kit_link_exe or kit_link_shared. The - * compiler owns the per-source KitObjBuilders for the lifetime of the - * link. */ +/* exe/shared path: compile every source via a single KitCompiler, load + * .o/.a/script inputs, and link. The link session borrows the per-source + * KitObjBuilders; this function keeps ownership and frees them after the + * session is done. */ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, const KitPreprocessOptions* pp) { KitContext ctx = driver_env_to_context(env); @@ -2565,6 +2301,7 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, KitLinkInputOrder* order = NULL; KitObjBuilder** objs = NULL; KitLinkScript* script = NULL; + KitSlice* rpath_slices = NULL; KitCCompileOptions copts; uint32_t nsrc = o->nsource_files + o->nsource_memory; uint32_t i; @@ -2651,10 +2388,10 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, dso_names[i] = kit_slice_cstr(o->dsos[i].path); } - if (o->linker_script) { + if (o->link.linker_script) { KitSlice dummy; - if (driver_load_bytes(io, CC_TOOL, o->linker_script, &script_lf, &dummy) != - 0) + if (driver_load_bytes(io, CC_TOOL, o->link.linker_script, &script_lf, + &dummy) != 0) goto out; } @@ -2697,39 +2434,11 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, { KitLinkSessionOptions lopts; KitStatus st; - KitSlice* rpath_slices = NULL; - if (o->nrpaths) { - rpath_slices = - driver_alloc_zeroed(env, o->nrpaths * sizeof(*rpath_slices)); - if (!rpath_slices) { - driver_errf(CC_TOOL, "out of memory"); - goto out; - } - for (i = 0; i < o->nrpaths; ++i) - rpath_slices[i] = kit_slice_cstr(o->rpaths[i]); - } - memset(&lopts, 0, sizeof(lopts)); - lopts.output_kind = - o->shared ? KIT_LINK_OUTPUT_SHARED : KIT_LINK_OUTPUT_EXE; - 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; - lopts.strip_debug = o->strip_debug; - lopts.pie = driver_link_pie(o->target, o->pie, o->shared, /*reloc=*/0); - 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; + if (driver_link_flags_fill_options( + &o->link, o->target, o->pie, o->shared, /*relocatable=*/0, + o->shared ? KIT_LINK_OUTPUT_SHARED : KIT_LINK_OUTPUT_EXE, script, + &lopts, &rpath_slices) != 0) + goto out; /* Translate the command-line link order into the engine's public * KitLinkInputOrder list. The dead-simple fallback for o->nlink_items == 0 @@ -2803,6 +2512,7 @@ out: } } if (script) kit_link_script_free(&ctx, script); + driver_link_flags_free_rpath_slices(&o->link, rpath_slices); if (compiler) driver_compiler_free(compiler); kit_target_free(target); driver_release_bytes(io, &script_lf); @@ -2831,7 +2541,10 @@ out: if (src_lf) driver_free(env, src_lf, o->nsource_files * sizeof(*src_lf)); if (src_bytes) driver_free(env, src_bytes, o->nsource_files * sizeof(*src_bytes)); - if (objs) driver_free(env, objs, nsrc * sizeof(*objs)); + if (objs) { + for (i = 0; i < nsrc; ++i) kit_obj_builder_free(objs[i]); + driver_free(env, objs, nsrc * sizeof(*objs)); + } return rc; } diff --git a/driver/lib/link_engine.h b/driver/lib/link_engine.h @@ -13,7 +13,7 @@ * `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 -> objs[index] (in-memory builder; borrowed) * 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] @@ -36,7 +36,9 @@ typedef struct 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. */ + * always freed before return; `out` is neither opened nor closed here. + * In-memory builders are borrowed for the duration of the session; callers + * retain ownership and must free them after this returns. */ KitStatus driver_link_engine_emit(KitCompiler* compiler, const KitLinkSessionOptions* lopts, const DriverLinkInputs* in, KitWriter* out); diff --git a/driver/lib/link_flags.c b/driver/lib/link_flags.c @@ -0,0 +1,441 @@ +#include "link_flags.h" + +#include <string.h> + +static int lf_tok_eq(const char* tok, size_t n, const char* lit) { + size_t ln = driver_strlen(lit); + return n == ln && driver_strneq(tok, lit, ln); +} + +static int lf_tok_prefix(const char* tok, size_t n, const char* lit) { + size_t ln = driver_strlen(lit); + return n >= ln && driver_strneq(tok, lit, ln); +} + +static int lf_grow_rpaths(DriverLinkFlags* lf) { + uint32_t old_cap = lf->cap_rpaths; + uint32_t new_cap = old_cap ? old_cap * 2u : 8u; + const char** nr; + if (new_cap <= old_cap) return 1; + nr = (const char**)driver_alloc_zeroed(lf->env, + (size_t)new_cap * sizeof(*nr)); + if (!nr) return 1; + if (lf->rpaths) { + driver_memcpy(nr, lf->rpaths, (size_t)lf->nrpaths * sizeof(*lf->rpaths)); + driver_free(lf->env, lf->rpaths, (size_t)old_cap * sizeof(*lf->rpaths)); + } + lf->rpaths = nr; + lf->cap_rpaths = new_cap; + return 0; +} + +static int lf_grow_owned(DriverLinkFlags* lf) { + uint32_t old_cap = lf->cap_owned_strings; + uint32_t new_cap = old_cap ? old_cap * 2u : 8u; + char** ns; + size_t* nsz; + if (new_cap <= old_cap) return 1; + ns = (char**)driver_alloc_zeroed(lf->env, (size_t)new_cap * sizeof(*ns)); + nsz = (size_t*)driver_alloc_zeroed(lf->env, (size_t)new_cap * sizeof(*nsz)); + if (!ns || !nsz) { + if (ns) driver_free(lf->env, ns, (size_t)new_cap * sizeof(*ns)); + if (nsz) driver_free(lf->env, nsz, (size_t)new_cap * sizeof(*nsz)); + return 1; + } + if (lf->owned_strings) { + driver_memcpy(ns, lf->owned_strings, + (size_t)lf->nowned_strings * sizeof(*lf->owned_strings)); + driver_free(lf->env, lf->owned_strings, + (size_t)old_cap * sizeof(*lf->owned_strings)); + } + if (lf->owned_string_sizes) { + driver_memcpy(nsz, lf->owned_string_sizes, + (size_t)lf->nowned_strings * + sizeof(*lf->owned_string_sizes)); + driver_free(lf->env, lf->owned_string_sizes, + (size_t)old_cap * sizeof(*lf->owned_string_sizes)); + } + lf->owned_strings = ns; + lf->owned_string_sizes = nsz; + lf->cap_owned_strings = new_cap; + return 0; +} + +static int lf_take_owned(DriverLinkFlags* lf, char* s, size_t n) { + if (lf->nowned_strings >= lf->cap_owned_strings && lf_grow_owned(lf) != 0) + return 1; + lf->owned_strings[lf->nowned_strings] = s; + lf->owned_string_sizes[lf->nowned_strings] = n; + lf->nowned_strings++; + return 0; +} + +static char* lf_dup_owned(DriverLinkFlags* lf, const char* s, size_t n) { + size_t bytes = n + 1u; + char* buf; + if (bytes == 0) return NULL; + buf = (char*)driver_alloc(lf->env, bytes); + if (!buf) return NULL; + driver_memcpy(buf, s, n); + buf[n] = '\0'; + if (lf_take_owned(lf, buf, bytes) != 0) { + driver_free(lf->env, buf, bytes); + return NULL; + } + return buf; +} + +static void lf_free_build_id(DriverLinkFlags* lf) { + if (lf->build_id_bytes) { + driver_free(lf->env, lf->build_id_bytes, lf->build_id_size); + lf->build_id_bytes = NULL; + lf->build_id_len = 0; + lf->build_id_size = 0; + } +} + +static int lf_hex_val(char c, unsigned* out) { + if (c >= '0' && c <= '9') { + *out = (unsigned)(c - '0'); + return 0; + } + if (c >= 'a' && c <= 'f') { + *out = (unsigned)(c - 'a' + 10); + return 0; + } + if (c >= 'A' && c <= 'F') { + *out = (unsigned)(c - 'A' + 10); + return 0; + } + return 1; +} + +static int lf_parse_hex_bytes(DriverLinkFlags* lf, const char* s, size_t n, + uint8_t** out_bytes, uint32_t* out_len, + size_t* out_size) { + uint8_t* bs; + size_t i; + if (n == 0 || (n & 1u) || n / 2u > UINT32_MAX) return 1; + bs = (uint8_t*)driver_alloc(lf->env, n / 2u); + if (!bs) return 1; + for (i = 0; i < n; i += 2u) { + unsigned hi, lo; + if (lf_hex_val(s[i], &hi) != 0 || lf_hex_val(s[i + 1u], &lo) != 0) { + driver_free(lf->env, bs, n / 2u); + return 1; + } + bs[i / 2u] = (uint8_t)((hi << 4) | lo); + } + *out_bytes = bs; + *out_len = (uint32_t)(n / 2u); + *out_size = n / 2u; + return 0; +} + +static int lf_record_build_id_span(DriverLinkFlags* lf, const char* val, + size_t n) { + if (n == 4 && driver_strneq(val, "none", 4)) { + lf_free_build_id(lf); + lf->build_id_mode = KIT_BUILDID_NONE; + return 0; + } + if (n == 6 && driver_strneq(val, "sha256", 6)) { + lf_free_build_id(lf); + lf->build_id_mode = KIT_BUILDID_SHA256; + return 0; + } + if (n == 4 && driver_strneq(val, "uuid", 4)) { + lf_free_build_id(lf); + lf->build_id_mode = KIT_BUILDID_UUID; + return 0; + } + if (n >= 2 && driver_strneq(val, "0x", 2)) { + uint8_t* bytes = NULL; + uint32_t len = 0; + size_t size = 0; + if (lf_parse_hex_bytes(lf, val + 2, n - 2u, &bytes, &len, &size) != 0) { + driver_errf(lf->tool, + "--build-id=0x... requires an even-length hex string"); + return 1; + } + lf_free_build_id(lf); + lf->build_id_bytes = bytes; + lf->build_id_len = len; + lf->build_id_size = size; + lf->build_id_mode = KIT_BUILDID_USER; + return 0; + } + driver_errf(lf->tool, "unknown --build-id value: %.*s", (int)n, val); + return 1; +} + +static int lf_record_rpath(DriverLinkFlags* lf, const char* s, size_t n) { + char* buf; + if (lf->nrpaths >= lf->cap_rpaths && lf_grow_rpaths(lf) != 0) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + buf = lf_dup_owned(lf, s, n); + if (!buf) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + lf->rpaths[lf->nrpaths++] = buf; + return 0; +} + +static int lf_record_soname(DriverLinkFlags* lf, const char* s, size_t n) { + char* buf = lf_dup_owned(lf, s, n); + if (!buf) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + lf->soname = buf; + return 0; +} + +static int lf_record_interp(DriverLinkFlags* lf, const char* s, size_t n) { + char* buf = lf_dup_owned(lf, s, n); + if (!buf) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + lf->interp_path = buf; + return 0; +} + +static int lf_subsystem_eq(const char* val, size_t n, const char* want) { + size_t i; + for (i = 0; want[i]; ++i) { + char a; + char 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] == ','; +} + +int driver_link_flags_init(DriverLinkFlags* lf, DriverEnv* env, + const char* tool, uint32_t initial_cap) { + uint32_t cap = initial_cap ? initial_cap : 8u; + memset(lf, 0, sizeof(*lf)); + lf->env = env; + lf->tool = tool; + lf->new_dtags = 1; + lf->cap_rpaths = cap; + lf->cap_owned_strings = cap; + lf->rpaths = + (const char**)driver_alloc_zeroed(env, (size_t)cap * sizeof(*lf->rpaths)); + lf->owned_strings = + (char**)driver_alloc_zeroed(env, (size_t)cap * sizeof(*lf->owned_strings)); + lf->owned_string_sizes = (size_t*)driver_alloc_zeroed( + env, (size_t)cap * sizeof(*lf->owned_string_sizes)); + if (!lf->rpaths || !lf->owned_strings || !lf->owned_string_sizes) { + driver_link_flags_fini(lf); + return 1; + } + return 0; +} + +void driver_link_flags_fini(DriverLinkFlags* lf) { + uint32_t i; + if (!lf || !lf->env) return; + for (i = 0; i < lf->nowned_strings; ++i) { + if (lf->owned_strings[i]) + driver_free(lf->env, lf->owned_strings[i], lf->owned_string_sizes[i]); + } + lf_free_build_id(lf); + if (lf->rpaths) + driver_free(lf->env, lf->rpaths, + (size_t)lf->cap_rpaths * sizeof(*lf->rpaths)); + if (lf->owned_strings) + driver_free(lf->env, lf->owned_strings, + (size_t)lf->cap_owned_strings * sizeof(*lf->owned_strings)); + if (lf->owned_string_sizes) + driver_free(lf->env, lf->owned_string_sizes, + (size_t)lf->cap_owned_strings * + sizeof(*lf->owned_string_sizes)); + memset(lf, 0, sizeof(*lf)); +} + +int driver_link_flags_record_build_id(DriverLinkFlags* lf, const char* val) { + return lf_record_build_id_span(lf, val, driver_strlen(val)); +} + +int driver_link_flags_record_pe_subsystem(DriverLinkFlags* lf, + const char* val, size_t n) { + if (lf_subsystem_eq(val, n, "CONSOLE") || + lf_subsystem_eq(val, n, "CUI")) { + lf->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI; + return 0; + } + if (lf_subsystem_eq(val, n, "WINDOWS") || + lf_subsystem_eq(val, n, "GUI")) { + lf->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI; + return 0; + } + driver_errf(lf->tool, "unsupported subsystem: %.*s", (int)n, val); + return 1; +} + +int driver_link_flags_record_wl(DriverLinkFlags* lf, const char* arg) { + const char* p = arg; + int expect_rpath = 0; + int expect_soname = 0; + int expect_interp = 0; + int expect_subsystem = 0; + while (*p) { + const char* tok = p; + size_t n = 0; + while (p[n] && p[n] != ',') ++n; + p = tok + n + (tok[n] == ',' ? 1u : 0u); + + if (expect_rpath || expect_soname || expect_interp || expect_subsystem) { + int rc = 0; + if (expect_rpath) + rc = lf_record_rpath(lf, tok, n); + else if (expect_soname) + rc = lf_record_soname(lf, tok, n); + else if (expect_interp) + rc = lf_record_interp(lf, tok, n); + else if (expect_subsystem) + rc = driver_link_flags_record_pe_subsystem(lf, tok, n); + if (rc != 0) return 1; + expect_rpath = expect_soname = expect_interp = expect_subsystem = 0; + continue; + } + + if (lf_tok_prefix(tok, n, "-soname=")) { + if (lf_record_soname(lf, tok + 8, n - 8u) != 0) return 1; + continue; + } + if (lf_tok_eq(tok, n, "-soname")) { + expect_soname = 1; + continue; + } + if (lf_tok_prefix(tok, n, "-rpath=")) { + if (lf_record_rpath(lf, tok + 7, n - 7u) != 0) return 1; + continue; + } + if (lf_tok_eq(tok, n, "-rpath")) { + expect_rpath = 1; + continue; + } + if (lf_tok_prefix(tok, n, "-dynamic-linker=")) { + if (lf_record_interp(lf, tok + 16, n - 16u) != 0) return 1; + continue; + } + if (lf_tok_eq(tok, n, "-dynamic-linker")) { + expect_interp = 1; + continue; + } + if (lf_tok_eq(tok, n, "--enable-new-dtags")) { + lf->new_dtags = 1; + continue; + } + if (lf_tok_eq(tok, n, "--disable-new-dtags")) { + lf->new_dtags = 0; + continue; + } + if (lf_tok_eq(tok, n, "--gc-sections")) { + lf->gc_sections = 1; + continue; + } + if (lf_tok_eq(tok, n, "--no-gc-sections")) { + lf->gc_sections = 0; + continue; + } + if (lf_tok_eq(tok, n, "-S") || lf_tok_eq(tok, n, "--strip-debug")) { + lf->strip_debug = 1; + continue; + } + if (lf_tok_prefix(tok, n, "--build-id=")) { + if (lf_record_build_id_span(lf, tok + 11, n - 11u) != 0) return 1; + continue; + } + if (lf_tok_eq(tok, n, "--build-id")) { + lf_free_build_id(lf); + lf->build_id_mode = KIT_BUILDID_SHA256; + continue; + } + if (lf_tok_prefix(tok, n, "--subsystem=")) { + if (driver_link_flags_record_pe_subsystem(lf, tok + 12, n - 12u) != 0) + return 1; + continue; + } + if (lf_tok_eq(tok, n, "--subsystem")) { + expect_subsystem = 1; + continue; + } + if (lf_tok_prefix(tok, n, "/SUBSYSTEM:")) { + if (driver_link_flags_record_pe_subsystem(lf, tok + 11, n - 11u) != 0) + return 1; + continue; + } + + driver_errf(lf->tool, "unsupported -Wl, token: %.*s", (int)n, tok); + return 1; + } + if (expect_rpath || expect_soname || expect_interp || expect_subsystem) { + driver_errf(lf->tool, "-Wl option requires another comma argument"); + return 1; + } + return 0; +} + +int driver_link_flags_fill_options(const DriverLinkFlags* lf, + KitTargetSpec target, int explicit_pie, + int shared, int relocatable, + uint8_t output_kind, + const KitLinkScript* script, + KitLinkSessionOptions* lopts, + KitSlice** rpath_slices_out) { + KitSlice* rpath_slices = NULL; + uint32_t i; + *rpath_slices_out = NULL; + if (lf->nrpaths) { + rpath_slices = (KitSlice*)driver_alloc_zeroed( + lf->env, (size_t)lf->nrpaths * sizeof(*rpath_slices)); + if (!rpath_slices) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + for (i = 0; i < lf->nrpaths; ++i) + rpath_slices[i] = kit_slice_cstr(lf->rpaths[i]); + } + memset(lopts, 0, sizeof(*lopts)); + lopts->output_kind = output_kind; + lopts->entry = kit_slice_cstr(lf->entry); + lopts->linker_script = script; + lopts->build_id_mode = lf->build_id_mode; + lopts->build_id_bytes = lf->build_id_bytes; + lopts->build_id_len = lf->build_id_len; + lopts->gc_sections = lf->gc_sections != 0; + lopts->strip_debug = lf->strip_debug != 0; + lopts->pie = + driver_link_pie(target, explicit_pie, shared, relocatable) != 0; + lopts->pe_subsystem = lf->pe_subsystem; + lopts->interp_path = kit_slice_cstr(lf->interp_path); + lopts->soname = kit_slice_cstr(lf->soname); + if (lf->new_dtags) { + lopts->runpaths = rpath_slices; + lopts->nrunpaths = lf->nrpaths; + } else { + lopts->rpaths = rpath_slices; + lopts->nrpaths = lf->nrpaths; + } + lopts->allow_undefined = 1; + *rpath_slices_out = rpath_slices; + return 0; +} + +void driver_link_flags_free_rpath_slices(const DriverLinkFlags* lf, + KitSlice* rpath_slices) { + if (rpath_slices) + driver_free(lf->env, rpath_slices, + (size_t)lf->nrpaths * sizeof(*rpath_slices)); +} diff --git a/driver/lib/link_flags.h b/driver/lib/link_flags.h @@ -0,0 +1,56 @@ +#ifndef KIT_DRIVER_LINK_FLAGS_H +#define KIT_DRIVER_LINK_FLAGS_H + +#include <kit/link.h> +#include <stddef.h> +#include <stdint.h> + +#include "driver.h" + +typedef struct DriverLinkFlags { + DriverEnv* env; + const char* tool; + + const char* entry; + const char* linker_script; + uint16_t pe_subsystem; /* KitPeSubsystem */ + const char* soname; + const char* interp_path; + const char** rpaths; + uint32_t nrpaths; + uint32_t cap_rpaths; + int new_dtags; /* 1=DT_RUNPATH (default), 0=DT_RPATH */ + int gc_sections; + int strip_debug; + + uint8_t build_id_mode; /* KitBuildIdMode */ + uint8_t* build_id_bytes; + uint32_t build_id_len; + size_t build_id_size; + + char** owned_strings; + size_t* owned_string_sizes; + uint32_t nowned_strings; + uint32_t cap_owned_strings; +} DriverLinkFlags; + +int driver_link_flags_init(DriverLinkFlags* lf, DriverEnv* env, + const char* tool, uint32_t initial_cap); +void driver_link_flags_fini(DriverLinkFlags* lf); + +int driver_link_flags_record_wl(DriverLinkFlags* lf, const char* arg); +int driver_link_flags_record_build_id(DriverLinkFlags* lf, const char* val); +int driver_link_flags_record_pe_subsystem(DriverLinkFlags* lf, + const char* val, size_t n); + +int driver_link_flags_fill_options(const DriverLinkFlags* lf, + KitTargetSpec target, int explicit_pie, + int shared, int relocatable, + uint8_t output_kind, + const KitLinkScript* script, + KitLinkSessionOptions* lopts, + KitSlice** rpath_slices_out); +void driver_link_flags_free_rpath_slices(const DriverLinkFlags* lf, + KitSlice* rpath_slices); + +#endif diff --git a/mk/driver_srcs.mk b/mk/driver_srcs.mk @@ -64,6 +64,7 @@ DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ LD RUN,dri 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,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/link_flags.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) diff --git a/mk/test.mk b/mk/test.mk @@ -67,6 +67,7 @@ TEST_TARGETS = \ test-driver-cas \ test-driver-cc \ test-driver-build \ + test-driver-lsan \ test-driver-objcopy \ test-driver-objdump \ test-driver-pkg \ @@ -201,6 +202,15 @@ test-driver-cc: bin test-driver-build: bin @KIT=$(abspath $(BIN)) sh test/buildcmds/run.sh +ifeq ($(HOST_OS),linux) +test-driver-lsan: + @$(MAKE) test-driver RELEASE=0 BUILD_DIR=build/lsan \ + ASAN_OPTIONS=detect_leaks=1:halt_on_error=1:abort_on_error=1 +else +test-driver-lsan: + @printf '%s\n' 'SKIP test-driver-lsan: LeakSanitizer requires HOST_OS=linux' +endif + # test-cbackend: --emit=c C-source backend, driven through three # frontends — parse-runner (C), toy-runner (toy), wasm-runner (wat/wasm). # Each invokes its existing runner with paths=C so a single corpus per @@ -546,6 +556,7 @@ TEST_RT_DEP = $(_TEST_RT_$(KIT_TEST_ARCH)) # reference. KIT_RT_RUNTIME_ARCHES mirrors the default in test/rt/run.sh. KIT_RT_RUNTIME_ARCHES ?= aa64 x64 rv64 RT_RUNTIME_DEPS := $(foreach a,$(KIT_RT_RUNTIME_ARCHES),$(_TEST_RT_$(a))) +LINK_EXE_RUNNER = build/test/link-exe-runner test-rt-runtime: bin $(RT_RUNTIME_DEPS) $(LINK_EXE_RUNNER) @bash test/rt/run.sh @@ -566,7 +577,6 @@ COFF_IMPORT_SMOKE_BIN = build/test/pe-import-smoke COFF_IMPORT_MINGW_BIN = build/test/pe-import-mingw COFF_DSO_FORWARDER_BIN = build/test/pe-dso-forwarder COFF_MIXED_ARCHIVE_BIN = build/test/pe-mixed-archive -LINK_EXE_RUNNER = build/test/link-exe-runner JIT_RUNNER = build/test/jit-runner PARSE_RUNNER = build/test/parse-runner ASM_RUNNER = build/test/asm-runner diff --git a/test/buildcmds/run.sh b/test/buildcmds/run.sh @@ -126,6 +126,11 @@ run_fail bo-neg-ir-needs-opt "$KIT" build-obj --emit=ir prog.toy -o x.ir 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 +# A missing -x argument before a --group source separator should not treat `--` +# as a language name. +run_fail bo-neg-group-x-missing-arg "$KIT" build-obj --group -x -- hello.c +contains bo-neg-group-x-missing-arg-diag "$work/bo-neg-group-x-missing-arg.err" \ + "requires an argument" # -dynamic is a build-lib concept. run_fail bo-neg-dynamic "$KIT" build-obj -dynamic prog.toy # unknown flag. @@ -155,6 +160,11 @@ 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 +# build-exe should reject the shared-library spelling instead of ignoring it. +run_fail be-neg-shared "$KIT" build-exe -shared -lc -Iinc hello.c helper.c \ + -o shared-nope +contains be-neg-shared-diag "$work/be-neg-shared.err" "shared" + # 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