kit

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

commit 8e44a1d43cfe18ffe958c88434c62ee2fa6cef34
parent 57dce832a3603adb73f196313e0859e19946bac2
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 16 Jul 2026 10:12:33 -0700

driver: make binary input handling transactional

Diffstat:
Mdriver/cmd/ld.c | 49++++++++++++++++++++++++++++++++++++++++---------
Mdriver/cmd/size.c | 110++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Atest/diagnostics/run.sh | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 204 insertions(+), 46 deletions(-)

diff --git a/driver/cmd/ld.c b/driver/cmd/ld.c @@ -721,8 +721,7 @@ static int ld_set_target_triple(LdOptions* o, const char* triple) { return 1; } if (driver_target_from_triple(triple, &t) != 0) { - driver_errf(LD_TOOL, "unrecognized target triple: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(triple))); + driver_err_unknown_target(LD_TOOL, triple); return 1; } o->windows_ucrt_target = strstr(triple, "windows-gnullvm") != NULL; @@ -3115,6 +3114,30 @@ static int ld_run_link(LdOptions* o) { KitTargetOptions topts; memset(&topts, 0, sizeof topts); topts.spec = o->target; + /* A linker target auto-detected from RISC-V ELF inputs already carries + * the psABI in spec.float_abi. kit_target_new normally derives that + * field from -march/-mabi, so reproduce the detected profile here rather + * than letting the rv32 default (ilp32 soft-float) overwrite ilp32f/d. + * The ISA matters for rv32 hardware ABIs because its conservative default + * deliberately omits F/D; rv64's default already includes both. */ + if (o->target.arch == KIT_ARCH_RV32) { + if (o->target.float_abi == KIT_FLOAT_ABI_SINGLE) { + topts.isa = kit_slice_cstr("rv32imafc_zicsr_zifencei"); + topts.abi = kit_slice_cstr("ilp32f"); + } else if (o->target.float_abi == KIT_FLOAT_ABI_DOUBLE) { + topts.isa = kit_slice_cstr("rv32imafdc_zicsr_zifencei"); + topts.abi = kit_slice_cstr("ilp32d"); + } else if (o->target.float_abi == KIT_FLOAT_ABI_SOFT) { + topts.abi = kit_slice_cstr("ilp32"); + } + } else if (o->target.arch == KIT_ARCH_RV64) { + if (o->target.float_abi == KIT_FLOAT_ABI_SINGLE) + topts.abi = kit_slice_cstr("lp64f"); + else if (o->target.float_abi == KIT_FLOAT_ABI_DOUBLE) + topts.abi = kit_slice_cstr("lp64d"); + else if (o->target.float_abi == KIT_FLOAT_ABI_SOFT) + topts.abi = kit_slice_cstr("lp64"); + } if (kit_target_new(&ctx, &topts, &target) != KIT_OK || driver_compiler_new(target, &ctx, &compiler) != KIT_OK) { driver_errf(LD_TOOL, "failed to initialize compiler"); @@ -3131,12 +3154,6 @@ static int ld_run_link(LdOptions* o) { } } - if (io->open_writer(io->user, o->output_path, &writer) != KIT_OK) { - driver_errf(LD_TOOL, "failed to open output: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(o->output_path))); - goto out; - } - /* Translate the recorded link-item order into the engine's KitLinkInputOrder * list. ld records no compiled-source items, so the source args are NULL. */ if (o->inputs.nlink_items) { @@ -3213,6 +3230,17 @@ static int ld_run_link(LdOptions* o) { break; } } + /* Parse and validate the complete input set before an output writer is + * opened. Apart from preserving the primary named-input diagnostic, this + * means a malformed object/archive/DSO or a link-resolution failure cannot + * create even a temporary output artifact. */ + if (st == KIT_OK) st = kit_link_session_resolve(link); + if (st == KIT_OK && + io->open_writer(io->user, o->output_path, &writer) != KIT_OK) { + driver_errf(LD_TOOL, "failed to open output: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(o->output_path))); + st = KIT_IO; + } if (st == KIT_OK) st = kit_link_session_emit(link, writer); if (st == KIT_OK) st = ld_write_link_report(o, io, link, o->lf.map_path, 0); if (st == KIT_OK) @@ -3224,7 +3252,10 @@ static int ld_run_link(LdOptions* o) { } out: - if (writer) kit_writer_close(writer); + if (writer) { + if (rc != 0) driver_writer_abort(writer); + kit_writer_close(writer); + } kit_link_session_free(link); /* Match compiler/linker drivers: successful link outputs get executable * file modes while still respecting the process umask. Done after closing diff --git a/driver/cmd/size.c b/driver/cmd/size.c @@ -63,6 +63,12 @@ static void size_print_berkeley_header(const SizeOpts* opts) { "data", w, "bss", "dec", "hex"); } +static void size_begin_output(const SizeOpts* opts, int* begun) { + if (*begun) return; + if (opts->fmt == SIZE_FMT_BERKELEY) size_print_berkeley_header(opts); + *begun = 1; +} + static void size_print_berkeley_sums(const KitObjSizeTotals* a, const char* name, const SizeOpts* opts) { int w = size_width(opts->radix); @@ -100,33 +106,39 @@ static void size_print_sysv(KitObjFile* of, const char* name, static int size_process_file(const KitContext* ctx, const KitSlice* input, const char* path, const SizeOpts* opts, - KitObjSizeTotals* total_out, int* any_out) { + KitObjSizeTotals* total_out, int* any_out, + int* output_begun) { KitBinFmt fmt = kit_detect_fmt(input->data, input->len); if (fmt == KIT_BIN_AR) { KitArIter* it = NULL; + int failed = 0; + int valid_members = 0; if (kit_ar_iter_new(ctx, input, &it) != KIT_OK) { driver_errf(SIZE_TOOL, "%s: not a recognized archive", path); return 1; } for (;;) { KitArMember m; - if (kit_ar_iter_next(it, &m) != KIT_ITER_ITEM) break; + KitIterResult ir = kit_ar_iter_next(it, &m); + if (ir == KIT_ITER_END) break; + if (ir == KIT_ITER_ERROR) { + driver_errf(SIZE_TOOL, "%s: corrupt archive", path); + failed = 1; + break; + } KitObjFile* of = NULL; KitSlice mb; + char nmbuf[512]; mb.data = m.data; mb.len = m.size; - if (kit_obj_open(ctx, KIT_SLICE_NULL, &mb, &of) == KIT_OK) { + snprintf(nmbuf, sizeof nmbuf, "%s(%.*s)", path, (int)m.name.len, + m.name.s); + if (kit_obj_open(ctx, kit_slice_cstr(nmbuf), &mb, &of) == KIT_OK) { KitObjSizeTotals a = size_compute_obj(of, opts); + size_begin_output(opts, output_begun); if (opts->fmt == SIZE_FMT_BERKELEY) { - char nmbuf[512]; - snprintf(nmbuf, sizeof nmbuf, "%.*s(%.*s)", - path ? (int)strlen(path) : 0, path ? path : "", - (int)m.name.len, m.name.s); size_print_berkeley_sums(&a, nmbuf, opts); } else { - char nmbuf[512]; - snprintf(nmbuf, sizeof nmbuf, "%s(%.*s)", path, (int)m.name.len, - m.name.s); size_print_sysv(of, nmbuf, opts); } total_out->text += a.text; @@ -134,10 +146,19 @@ static int size_process_file(const KitContext* ctx, const KitSlice* input, total_out->bss += a.bss; total_out->total += a.total; *any_out = 1; + ++valid_members; kit_obj_free(of); + } else { + driver_errf(SIZE_TOOL, "%s: not a recognized object file", nmbuf); + failed = 1; } } kit_ar_iter_free(it); + if (!valid_members && !failed) { + driver_errf(SIZE_TOOL, "%s: archive contains no object members", path); + failed = 1; + } + return failed; } else { KitObjFile* of = NULL; if (kit_obj_open(ctx, kit_slice_cstr(path), input, &of) != KIT_OK) { @@ -146,6 +167,7 @@ static int size_process_file(const KitContext* ctx, const KitSlice* input, } { KitObjSizeTotals a = size_compute_obj(of, opts); + size_begin_output(opts, output_begun); if (opts->fmt == SIZE_FMT_BERKELEY) { size_print_berkeley_sums(&a, path, opts); } else { @@ -204,8 +226,9 @@ int driver_size(int argc, char** argv) { SizeOpts opts; KitObjSizeTotals totals; int i, rc = 1, any_input = 0, any_output = 0; + int output_begun = 0, had_error = 0, options_done = 0; - if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { + if (argc < 2) { driver_help_size(); return 0; } @@ -218,35 +241,48 @@ int driver_size(int argc, char** argv) { for (i = 1; i < argc; ++i) { const char* a = argv[i]; - if (driver_streq(a, "-A") || driver_streq(a, "--format=sysv")) { + if (!options_done && driver_streq(a, "--")) { + options_done = 1; + continue; + } + if (!options_done && + (driver_streq(a, "-h") || driver_streq(a, "--help"))) { + driver_help_size(); + rc = 0; + goto done; + } + if (!options_done && + (driver_streq(a, "-A") || driver_streq(a, "--format=sysv"))) { opts.fmt = SIZE_FMT_SYSV; continue; } - if (driver_streq(a, "-B") || driver_streq(a, "--format=berkeley")) { + if (!options_done && + (driver_streq(a, "-B") || driver_streq(a, "--format=berkeley"))) { opts.fmt = SIZE_FMT_BERKELEY; continue; } - if (driver_streq(a, "-d")) { + if (!options_done && driver_streq(a, "-d")) { opts.radix = SIZE_RAD_DEC; continue; } - if (driver_streq(a, "-o")) { + if (!options_done && driver_streq(a, "-o")) { opts.radix = SIZE_RAD_OCT; continue; } - if (driver_streq(a, "-x")) { + if (!options_done && driver_streq(a, "-x")) { opts.radix = SIZE_RAD_HEX; continue; } - if (driver_streq(a, "--common")) { + if (!options_done && driver_streq(a, "--common")) { opts.common = 1; continue; } - if (driver_streq(a, "-t") || driver_streq(a, "--totals")) { + if (!options_done && + (driver_streq(a, "-t") || driver_streq(a, "--totals"))) { opts.totals = 1; continue; } - if (a[0] == '-') { + if (!options_done && a[0] == '-') { driver_errf(SIZE_TOOL, "unknown option: %s", a); rc = 2; goto done; @@ -260,15 +296,19 @@ int driver_size(int argc, char** argv) { goto done; } - if (opts.fmt == SIZE_FMT_BERKELEY) size_print_berkeley_header(&opts); - + options_done = 0; for (i = 1; i < argc; ++i) { const char* a = argv[i]; - if (driver_streq(a, "-A") || driver_streq(a, "--format=sysv") || - driver_streq(a, "-B") || driver_streq(a, "--format=berkeley") || - driver_streq(a, "-d") || driver_streq(a, "-o") || - driver_streq(a, "-x") || driver_streq(a, "--common") || - driver_streq(a, "-t") || driver_streq(a, "--totals")) { + if (!options_done && driver_streq(a, "--")) { + options_done = 1; + continue; + } + if (!options_done && + (driver_streq(a, "-A") || driver_streq(a, "--format=sysv") || + driver_streq(a, "-B") || driver_streq(a, "--format=berkeley") || + driver_streq(a, "-d") || driver_streq(a, "-o") || + driver_streq(a, "-x") || driver_streq(a, "--common") || + driver_streq(a, "-t") || driver_streq(a, "--totals"))) { continue; } { @@ -276,22 +316,18 @@ int driver_size(int argc, char** argv) { DriverLoad ld = {0}; KitSlice input; if (driver_load_bytes(&env.file_io, SIZE_TOOL, path, &ld, &input) != 0) { - rc = 1; - goto done; - } - if (size_process_file(&ctx, &input, path, &opts, &totals, &any_output) != - 0) { - driver_release_bytes(&env.file_io, &ld); - rc = 1; - goto done; + had_error = 1; + continue; } + if (size_process_file(&ctx, &input, path, &opts, &totals, &any_output, + &output_begun) != 0) + had_error = 1; driver_release_bytes(&env.file_io, &ld); } } if (!any_output) { - driver_errf(SIZE_TOOL, "no input files"); - rc = 2; + rc = 1; goto done; } @@ -307,7 +343,7 @@ int driver_size(int argc, char** argv) { dec_buf, hex_buf); } - rc = 0; + rc = had_error ? 1 : 0; done: driver_env_fini(&env); return rc; diff --git a/test/diagnostics/run.sh b/test/diagnostics/run.sh @@ -0,0 +1,91 @@ +#!/bin/sh +# Regression checks for primary-input diagnostics and transactional output in +# ld/strip/size (KIT-P2-003). + +set -u + +script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd) +repo_root=$(CDPATH= cd -- "$script_dir/../.." && pwd) +KIT=${KIT:-$repo_root/build/kit} +KIT_KIT_DIR=$repo_root/test/lib +. "$repo_root/test/lib/kit_sh_kit.sh" + +kit_require_kit driver-diagnostics +kit_report_init +work=$(mktemp -d "${TMPDIR:-/tmp}/kit-diagnostics-test.XXXXXX") +trap 'rm -rf "$work"' EXIT + +expect_one() { + label=$1 + got=$2 + if [ "$got" -eq 1 ]; then + ok "$label" + else + printf 'got exit %s, want 1\n' "$got" >"$work/$label.diag" + not_ok "$label" "$work/$label.diag" + fi +} + +cat >"$work/valid.c" <<'EOF' +int diagnostic_fixture(void) { return 1; } +EOF +"$KIT" cc -ffreestanding -c "$work/valid.c" -o "$work/valid.o" \ + >"$work/compile.stdout" 2>"$work/compile.stderr" + +# A total size failure has no stdout. A mixed request retains the valid row but +# still returns operational failure for the missing input. +"$KIT" size "$work/missing.o" >"$work/size-missing.stdout" \ + 2>"$work/size-missing.stderr" +expect_one size-missing-status "$?" +if [ ! -s "$work/size-missing.stdout" ]; then + ok size-missing-stdout-empty +else + not_ok size-missing-stdout-empty "$work/size-missing.stdout" +fi +contains size-missing-path "$work/size-missing.stderr" "$work/missing.o" + +"$KIT" size "$work/valid.o" "$work/missing.o" \ + >"$work/size-mixed.stdout" 2>"$work/size-mixed.stderr" +expect_one size-mixed-status "$?" +contains size-mixed-valid-row "$work/size-mixed.stdout" "$work/valid.o" +contains size-mixed-bad-path "$work/size-mixed.stderr" "$work/missing.o" + +# Invalid strip input names the primary input and never publishes its output. +printf 'not an object\n' >"$work/invalid.o" +"$KIT" strip -o "$work/stripped.o" "$work/invalid.o" \ + >"$work/strip.stdout" 2>"$work/strip.stderr" +expect_one strip-invalid-status "$?" +contains strip-invalid-path "$work/strip.stderr" "$work/invalid.o" +if [ ! -e "$work/stripped.o" ]; then + ok strip-invalid-output-absent +else + not_ok strip-invalid-output-absent +fi + +# A recognized-but-truncated member is diagnosed as archive(member). +head -c 8 "$work/valid.o" >"$work/bad.o" +"$KIT" ar rcs "$work/bad.a" "$work/bad.o" \ + >"$work/ar.stdout" 2>"$work/ar.stderr" +"$KIT" strip -o "$work/bad-stripped.a" "$work/bad.a" \ + >"$work/strip-archive.stdout" 2>"$work/strip-archive.stderr" +expect_one strip-archive-status "$?" +contains strip-archive-member "$work/strip-archive.stderr" \ + "$work/bad.a(bad.o)" +if [ ! -e "$work/bad-stripped.a" ]; then + ok strip-archive-output-absent +else + not_ok strip-archive-output-absent +fi + +# ld resolves/validates before publication. An existing destination remains +# byte-identical on invalid input rather than becoming empty or partial. +cp "$work/invalid.o" "$work/ld-before.o" +cp "$work/ld-before.o" "$work/ld-out.o" +"$KIT" ld -r -o "$work/ld-out.o" "$work/invalid.o" \ + >"$work/ld.stdout" 2>"$work/ld.stderr" +expect_one ld-invalid-status "$?" +contains ld-invalid-path "$work/ld.stderr" "$work/invalid.o" +same_file ld-invalid-output-unchanged "$work/ld-before.o" "$work/ld-out.o" + +kit_summary driver-diagnostics +kit_exit