kit

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

commit de9bfacea7ed2f358f9838a35809d2a56cd1c990
parent cf332ff49fb4c2a596a7e677482531627462caca
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  9 Jun 2026 18:25:21 -0700

cleanup: parallel subsystem sweep (correctness, dead code, perf, dedup)

Correctness:
- frontend: __atomic_compare_exchange_n now uses c_abi_sizeof/alignof for the
  pointer + bool-result frame slots (fixes rv32 stack corruption); recurse
  find_record_member_path through arbitrary anon-struct nesting (was 2 levels)
- asm: decode_string free size now matches alloc size (heap-corruption fix);
  asm_frontend_compile checks NULL + propagates parse errors (was always KIT_OK)
- emu: emu_mem_load64 reads the full 8 bytes via emu_mem_load_raw instead of
  two load32s (no more split across a map boundary); EMU_MAX_NEEDED constant +
  KIT_LOGW on DT_NEEDED overflow instead of silent truncation
- link: keep .preinit_array input sections (link_section_kept was dropping them)
- driver: build-exe now copies sysroot lib dirs into -L (build_apply_hosted_profile
  matched to cc); -Werror honored at driver level; dep-iter reports write errors;
  driver_basename strips '\'; objdump seg-perms uses a caller buffer
- interp: computed-goto dispatch table moved off function-static globals
- N4 asm expected-punct diagnostic prints the actual punctuator

Dead code removed:
- opt: OPT_ALLOC_SPLIT + live-range-split machinery, opt_regalloc, OptPassCtx,
  OPT_ANALYSIS_CFG, x64 type_is_signed, RV_FRAME_SAVE_SIZE
- debug: dead loclist producer API, debug_remap_path decl, sibling_visited,
  DwString; config_stubs orphans
- frontend: type_tag_get, hs_intersect
- dist: dead v2 DistManifest emit/parse; emu_addr_space_set_perm; wasm dead
  tail-call helper; cg temp-locals no-op plumbing; kit_cg_abort alias

Perf: opt data-reloc rooting de-O(n^2) (section-indexed table); copy-cleanup
batched; debug str_index_of hashmap-backed; reused phase-1 alloc group info;
wasm per-function reg reset bounded by high-water mark.

Dedup: dist parse helpers (dist_parse.h); driver path-join/parse-u64/epoch/hex
helpers + load_bytes; strip/objcopy shared objedit.c; debug add_*_reloc; ABI
classify_void + int128-pair + align_up_u32; aa64 FP encoders via isa.h;
macho/elf byte buffers unified (obj/bytebuf.h); ELF DynSym* isolated from the
format-neutral header; abi_internal_type_info inlined; pool_intern_cstr.

API/diagnostics: kit_obj_symiter_new/dynsymiter_new take const; B5 link warns on
ignored shared/build-id opts instead of silent void-casts; O1opt IR dumps use
diag_emit not compiler_panic. _Float16 kept as the deliberate float alias; test
cc-emit-ir updated for the single-result IR dump format.

Diffstat:
Mdriver/cmd/ar.c | 14+-------------
Mdriver/cmd/build.c | 9+++++++++
Mdriver/cmd/cc.c | 55+++++++++++++++++++++++++++++++++++++++++++------------
Mdriver/cmd/cmp.c | 31++-----------------------------
Mdriver/cmd/disas.c | 37++++---------------------------------
Mdriver/cmd/ld.c | 42+++++++-----------------------------------
Mdriver/cmd/objcopy.c | 74+++++++-------------------------------------------------------------------
Mdriver/cmd/objdump.c | 18++++++++++--------
Mdriver/cmd/ranlib.c | 15++-------------
Mdriver/cmd/run.c | 14++++++++++++++
Mdriver/cmd/strip.c | 108+++++++------------------------------------------------------------------------
Mdriver/cmd/xxd.c | 44++++++--------------------------------------
Mdriver/driver.h | 47++++++++++++++++++++++++++++++-----------------
Mdriver/env.h | 10++++++++++
Mdriver/env/common.c | 167++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mdriver/env/posix.c | 46++++------------------------------------------
Mdriver/env/windows.c | 52++++++----------------------------------------------
Mdriver/lib/hosted.c | 33+++++++++------------------------
Mdriver/lib/lib_resolve.c | 33+++++++++++++--------------------
Mdriver/lib/link_flags.c | 21+++------------------
Adriver/lib/objedit.c | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adriver/lib/objedit.h | 33+++++++++++++++++++++++++++++++++
Mdriver/lib/runtime.c | 46+++++++++++++++++-----------------------------
Mdriver/lib/target.c | 7+++++++
Minclude/kit/cg.h | 3++-
Minclude/kit/object.h | 4++--
Mlang/c/parse/parse_expr.c | 124+++++++++++++++++++++++++++++--------------------------------------------------
Mlang/c/parse/parse_type.c | 4++++
Mlang/c/type/type.c | 7-------
Mlang/c/type/type.h | 1-
Mlang/cpp/pp/pp_directive.c | 9+++++++--
Mlang/cpp/pp/pp_expand.c | 27---------------------------
Mmk/driver_srcs.mk | 1+
Msrc/abi/abi.c | 48+++++++++++++++++++++++++++++++++++++++---------
Msrc/abi/abi_aapcs64.c | 31++++++-------------------------
Msrc/abi/abi_aapcs64_windows.c | 3+--
Msrc/abi/abi_apple_arm64.c | 3+--
Msrc/abi/abi_apple_x64.c | 3+--
Msrc/abi/abi_internal.h | 10++++++++--
Msrc/abi/abi_rv64.c | 25+++++++++----------------
Msrc/abi/abi_sysv_x64.c | 37+++++++++----------------------------
Msrc/abi/abi_win64_x64.c | 31++++++-------------------------
Msrc/api/compile.c | 19++++++++++++++++++-
Msrc/api/config_stubs.c | 14--------------
Msrc/api/link.c | 38++++++++++++++++++++++++++++++--------
Msrc/api/object_file.c | 9+++++----
Msrc/arch/aa64/native.c | 67++++++++++++++++++++++++++++++-------------------------------------
Msrc/arch/arch.h | 2+-
Msrc/arch/native_target.h | 7+++++++
Msrc/arch/riscv/disasm.c | 2+-
Msrc/arch/riscv/native.c | 13++++++-------
Msrc/arch/wasm/abi.c | 6+++---
Msrc/arch/wasm/emit.c | 29+++++++++--------------------
Msrc/arch/wasm/internal.h | 5++++-
Msrc/arch/x64/emit.h | 4----
Msrc/arch/x64/native.c | 5-----
Msrc/asm/asm.c | 40++++++++++++++++++++++++++++++----------
Msrc/asm/asm_lex.c | 5-----
Msrc/asm/asm_lex.h | 28----------------------------
Msrc/cg/arith.c | 3---
Msrc/cg/call.c | 21++++-----------------
Msrc/cg/control.c | 1-
Msrc/cg/fold.c | 35++---------------------------------
Msrc/cg/internal.h | 5-----
Msrc/cg/memory.c | 13-------------
Msrc/cg/session.c | 6+-----
Msrc/cg/value.c | 18------------------
Msrc/cg/wide.c | 4----
Msrc/debug/debug.c | 34----------------------------------
Msrc/debug/debug.h | 6------
Msrc/debug/debug_emit.c | 70+++++++++++++++++++++-------------------------------------------------
Msrc/debug/debug_internal.h | 33+++++----------------------------
Msrc/debug/dwarf_die.c | 11+++++++----
Msrc/debug/dwarf_internal.h | 7++-----
Msrc/debug/dwarf_type.c | 53++++++-----------------------------------------------
Msrc/dist/blob.c | 8++++++--
Msrc/dist/dist.h | 6++++++
Asrc/dist/dist_parse.h | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/dist/kpkg.c | 216++++++++++++++++++++++++++++++++++---------------------------------------------
Msrc/dist/kpkg.h | 6+++---
Msrc/dist/manifest.c | 455++++++++++++++++---------------------------------------------------------------
Msrc/dist/manifest.h | 43+++----------------------------------------
Msrc/dist/tree.c | 144+++++++++++++++++++++++++++++--------------------------------------------------
Msrc/emu/emu.h | 7+++++--
Msrc/emu/image.c | 5-----
Msrc/emu/runtime.c | 8++------
Msrc/interp/engine.c | 45++++++++++++++++++++++++---------------------
Msrc/interp/interp.h | 8++++++++
Msrc/link/link.c | 8++++----
Msrc/link/link_internal.h | 130++++---------------------------------------------------------------------------
Msrc/link/link_jit.c | 2+-
Msrc/link/link_layout.c | 4+++-
Asrc/obj/bytebuf.h | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/obj/coff/link.c | 2+-
Msrc/obj/elf/emu_load.c | 11+++++++----
Msrc/obj/elf/link.c | 1+
Msrc/obj/elf/link_dyn.c | 69+++++++++++++++------------------------------------------------------
Asrc/obj/elf/link_dyn.h | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/obj/macho/link.c | 474+++++++++++++++++++++++++++++++++++--------------------------------------------
Msrc/obj/obj_secnames.c | 2+-
Msrc/opt/ir.h | 20--------------------
Msrc/opt/opt.c | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/opt/opt.h | 4+---
Msrc/opt/opt_internal.h | 11+++--------
Msrc/opt/pass_analysis.c | 30+-----------------------------
Msrc/opt/pass_cfg.c | 5++---
Msrc/opt/pass_copy.c | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/opt/pass_inline.c | 4++--
Msrc/opt/pass_jump.c | 12++++++------
Msrc/opt/pass_lower.c | 342+++++++------------------------------------------------------------------------
Msrc/opt/pass_o2.c | 8++++----
Msrc/opt/pass_ssa.c | 4++--
Mtest/driver/run.sh | 2+-
113 files changed, 1891 insertions(+), 2777 deletions(-)

diff --git a/driver/cmd/ar.c b/driver/cmd/ar.c @@ -123,18 +123,6 @@ void driver_help_ar(void) { "usage\n"))); } -/* Parse SOURCE_DATE_EPOCH into a u64; 0 (or unset/invalid) means "no epoch". */ -static uint64_t ar_epoch_from_env(void) { - const char* s = driver_getenv("SOURCE_DATE_EPOCH"); - uint64_t v = 0; - if (!s || !*s) return 0; - for (; *s; ++s) { - if (*s < '0' || *s > '9') return 0; - v = v * 10 + (uint64_t)(*s - '0'); - } - return v; -} - /* Return 1 iff `name` matches any of the names in argv[start..argc) — or if * there are no filters, in which case every member matches. */ static int ar_name_selected(KitSlice name, int argc, char** argv, int start) { @@ -342,7 +330,7 @@ static int ar_do_write(DriverEnv* env, const char* archive_path, int nmembers, void** sym_allocs = NULL; size_t* sym_alloc_szs = NULL; - opts.epoch = ar_epoch_from_env(); + opts.epoch = driver_epoch_from_env(); opts.long_names = 1; /* `r`: read existing archive (if any) and seed `members` with it. */ diff --git a/driver/cmd/build.c b/driver/cmd/build.c @@ -1044,6 +1044,11 @@ static int build_apply_hosted_profile(BuildOptions* o) { 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]; + /* Add hosted lib search dirs so user -l flags (-lm, -lpthread, etc.) can be + * resolved. The strings are owned by o->hosted and outlive lib_search_paths. + * Insert before any user -L dirs so sysroot libs take precedence. */ + for (i = 0; i < o->hosted.nlib_search_dirs; ++i) + o->lib_search_paths[o->nlib_search_paths++] = o->hosted.lib_search_dirs[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) @@ -2290,6 +2295,10 @@ static int build_main(int argc, char** argv, int kind, const char* tool) { rc = build_run_per_source(&o, compiler, &ctx, &code, &diag); } + if (rc == 0 && + driver_diag_finish(&env, tool, o.warnings_are_errors, o.max_errors)) + rc = 1; + done: if (compiler) driver_compiler_free(compiler); kit_target_free(target); diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c @@ -132,7 +132,6 @@ typedef struct CcOptions { KitTargetSpec target; /* -target / host */ int target_set; /* did -target appear */ const char* output_path; /* -o */ - int output_path_set; char* owned_output_path; size_t owned_output_path_size; const char* sysroot; /* --sysroot / -isysroot */ @@ -1160,7 +1159,12 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { KIT_SLICE_ARG(kit_slice_cstr(a))); return 1; } - continue; + /* The preprocessor has no force-include hook yet (KitPreprocessOptions + * carries no prefix-include list), so honoring this would silently drop + * the file and miscompile. Fail loudly rather than ignore it. */ + driver_errf(CC_TOOL, "-include is unimplemented: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(argv[i]))); + return 1; } if (driver_streq(a, "-fPIC") || driver_streq(a, "-fpic")) { @@ -1291,12 +1295,10 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { return 1; } o->output_path = argv[i]; - o->output_path_set = 1; continue; } if (driver_strneq(a, "--output=", 9)) { o->output_path = a + 9; - o->output_path_set = 1; continue; } if (driver_streq(a, "--output")) { @@ -1305,7 +1307,6 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { return 1; } o->output_path = argv[i]; - o->output_path_set = 1; continue; } if (driver_streq(a, "-e")) { @@ -1917,18 +1918,40 @@ static char* cc_dep_default_path(DriverEnv* env, const char* out_path, } } +/* Collect the include-dependency edges into `list`. Returns 0 on success, + * CC_DEP_COLLECT_OOM on allocation failure (caller reports it generically), or + * CC_DEP_COLLECT_DIAGNOSED when the iterator faulted mid-scan -- in that case a + * specific diagnostic naming the offending dependency has already been emitted, + * so the caller must not overwrite it with a generic message. */ +#define CC_DEP_COLLECT_OOM 1 +#define CC_DEP_COLLECT_DIAGNOSED 2 + static int cc_dep_collect(DriverEnv* env, KitCompiler* compiler, int system_filter, CcDepList* list) { KitDepIter* it = NULL; - KitDepEdge e; - if (kit_dep_iter_new(compiler, &it) != KIT_OK) return 1; + KitDepEdge e = {0}; + if (kit_dep_iter_new(compiler, &it) != KIT_OK) return CC_DEP_COLLECT_OOM; for (;;) { KitIterResult r = kit_dep_iter_next(it, &e); + if (r == KIT_ITER_ERROR) { + /* Name the most recently scanned dependency so the failure is actionable + * instead of a silent short read of the dep list. `e` is zero-initialized + * and only ever written by kit_dep_iter_next, so it is never garbage: on + * an error before any edge it is empty (-> generic message), otherwise it + * holds the last edge we saw. */ + if (e.included_name.len) + driver_errf(CC_TOOL, "failed while scanning dependency: %.*s", + KIT_SLICE_ARG(e.included_name)); + else + driver_errf(CC_TOOL, "failed to scan include dependencies"); + kit_dep_iter_free(it); + return CC_DEP_COLLECT_DIAGNOSED; + } if (r != KIT_ITER_ITEM) break; if (system_filter && e.from_system_path) continue; if (cc_dep_list_push(env, list, e.included_name.s) != 0) { kit_dep_iter_free(it); - return 1; + return CC_DEP_COLLECT_OOM; } } kit_dep_iter_free(it); @@ -2005,10 +2028,14 @@ static int cc_dep_finish(DriverEnv* env, const KitContext* ctx, uint32_t ntargets; int rc = 1; - if (cc_dep_collect(env, compiler, cc_dep_filters_system(o->dep_mode), - &deps) != 0) { - driver_errf(CC_TOOL, "out of memory"); - goto out; + { + int cr = cc_dep_collect(env, compiler, cc_dep_filters_system(o->dep_mode), + &deps); + if (cr == CC_DEP_COLLECT_OOM) { + driver_errf(CC_TOOL, "out of memory"); + goto out; + } + if (cr != 0) goto out; /* CC_DEP_COLLECT_DIAGNOSED: already reported */ } targets = o->dep_targets; @@ -2800,6 +2827,10 @@ static int driver_cc_main(int argc, char** argv, int force_check) { rc = cc_run_link_exe(&env, &co, &pp); } + if (rc == 0 && + driver_diag_finish(&env, CC_TOOL, co.warnings_are_errors, co.max_errors)) + rc = 1; + cc_options_release(&co); if (runtime_resolved) driver_runtime_support_fini(&env, &runtime); driver_env_fini(&env); diff --git a/driver/cmd/cmp.c b/driver/cmd/cmp.c @@ -49,33 +49,6 @@ void driver_help_cmp(void) { "trouble/usage\n"))); } -/* Parse a decimal or 0x-hex non-negative integer. Returns 0 on success. */ -static int cmp_parse_u64(const char* s, uint64_t* out) { - uint64_t v = 0; - int base = 10; - if (!s || !*s) return 1; - if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { - base = 16; - s += 2; - if (!*s) return 1; - } - for (; *s; ++s) { - unsigned d; - char c = *s; - if (c >= '0' && c <= '9') - d = (unsigned)(c - '0'); - else if (base == 16 && c >= 'a' && c <= 'f') - d = (unsigned)(c - 'a' + 10); - else if (base == 16 && c >= 'A' && c <= 'F') - d = (unsigned)(c - 'A' + 10); - else - return 1; - v = v * (uint64_t)base + d; - } - *out = v; - return 0; -} - /* Load a named operand, or stdin when the name is "-". Returns 0 on success * with data and len set. A stdin read fills stdin_buf/stdin_len (freed with * driver_free); a file read fills ld (freed with driver_release_bytes). */ @@ -157,7 +130,7 @@ int driver_cmp(int argc, char** argv) { continue; } if (driver_streq(a, "-n")) { - if (i + 1 >= argc || cmp_parse_u64(argv[++i], &opts.max) != 0) { + if (i + 1 >= argc || driver_parse_u64(argv[++i], &opts.max) != 0) { driver_errf(CMP_TOOL, "-n requires a non-negative count"); goto done; } @@ -174,7 +147,7 @@ int driver_cmp(int argc, char** argv) { if (npos < 2) { names[npos] = a; } else if (npos < 4) { - if (cmp_parse_u64(a, &skip[npos - 2]) != 0) { + if (driver_parse_u64(a, &skip[npos - 2]) != 0) { driver_errf(CMP_TOOL, "invalid skip value: %s", a); goto done; } diff --git a/driver/cmd/disas.c b/driver/cmd/disas.c @@ -53,11 +53,6 @@ static int disas_is_hex(int c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } -static int disas_hexval(int c) { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - return c - 'A' + 10; -} /* Decode a whitespace-tolerant hex string into freshly-allocated bytes. * Returns 0 on success (caller frees via driver_free(env, *out, *outlen)). */ @@ -92,7 +87,7 @@ static int disas_parse_hex(DriverEnv* env, const char* s, uint8_t** out, for (p = s; *p; ++p) { int v; if (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') continue; - v = disas_hexval((unsigned char)*p); + v = driver_hex_nibble((char)(unsigned char)*p); if (hi < 0) hi = v; else { @@ -191,33 +186,9 @@ int driver_disas(int argc, char** argv) { driver_errf(DISAS_TOOL, "--base requires an address"); goto done; } - { - const char* v = argv[++i]; - uint64_t val = 0; - int base = 10; - if (v[0] == '0' && (v[1] == 'x' || v[1] == 'X')) { - base = 16; - v += 2; - } - if (!*v) { - driver_errf(DISAS_TOOL, "invalid --base address"); - goto done; - } - for (; *v; ++v) { - int d; - if (*v >= '0' && *v <= '9') - d = *v - '0'; - else if (base == 16 && *v >= 'a' && *v <= 'f') - d = *v - 'a' + 10; - else if (base == 16 && *v >= 'A' && *v <= 'F') - d = *v - 'A' + 10; - else { - driver_errf(DISAS_TOOL, "invalid --base address"); - goto done; - } - val = val * (uint64_t)base + (uint64_t)d; - } - o.base = val; + if (driver_parse_u64(argv[++i], &o.base) != 0) { + driver_errf(DISAS_TOOL, "invalid --base address"); + goto done; } continue; } diff --git a/driver/cmd/ld.c b/driver/cmd/ld.c @@ -445,36 +445,6 @@ static int ld_note_library_request(LdOptions* o, const char* name) { /* ---------- --build-id parsing ---------- */ -static int hex_nibble(char c) { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'a' && c <= 'f') return 10 + (c - 'a'); - if (c >= 'A' && c <= 'F') return 10 + (c - 'A'); - return -1; -} - -/* Parse a -Ttext address: 0x<hex> or decimal. Returns 0 on success. */ -static int ld_parse_addr(const char* s, uint64_t* out) { - uint64_t v = 0; - const char* p; - if (!s || !s[0]) return 1; - if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { - p = s + 2; - if (!*p) return 1; - for (; *p; ++p) { - int n = hex_nibble(*p); - if (n < 0) return 1; - v = (v << 4) | (uint64_t)n; - } - } else { - for (p = s; *p; ++p) { - if (*p < '0' || *p > '9') return 1; - v = v * 10u + (uint64_t)(*p - '0'); - } - } - *out = v; - return 0; -} - /* Parse `--build-id=...` argument into options. Accepts "none", "sha256", * "uuid", or "0x<even-hex>". Returns 0 on success, 1 on bad value. */ static int ld_parse_build_id(LdOptions* o, const char* val) { @@ -510,8 +480,8 @@ static int ld_parse_build_id(LdOptions* o, const char* val) { return 1; } for (i = 0; i < nbytes; ++i) { - int hi = hex_nibble(hex[2 * i]); - int lo = hex_nibble(hex[2 * i + 1]); + int hi = driver_hex_nibble(hex[2 * i]); + int lo = driver_hex_nibble(hex[2 * i + 1]); if (hi < 0 || lo < 0) { driver_errf(LD_TOOL, "--build-id: invalid hex digit"); driver_free(o->env, buf, nbytes); @@ -873,7 +843,7 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { } if (tval) { uint64_t v; - if (ld_parse_addr(tval, &v) != 0) { + if (driver_parse_u64(tval, &v) != 0) { driver_errf(LD_TOOL, "-Ttext: invalid address: %s", tval); return 1; } @@ -903,16 +873,18 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { o->support_dir = a + 14; continue; } + /* --sysroot is authoritative in the first pass (GNU ld treats it as a + * global search-prefix that must be known before any -l / "=" rewrite), so + * o->sysroot is already set. Here we only consume the tokens so they are + * not misread as input files; do not re-apply the value. */ if (driver_streq(a, "--sysroot")) { if (++i >= argc) { driver_errf(LD_TOOL, "--sysroot requires an argument"); return 1; } - o->sysroot = argv[i]; continue; } if (driver_strneq(a, "--sysroot=", 10)) { - o->sysroot = a + 10; continue; } diff --git a/driver/cmd/objcopy.c b/driver/cmd/objcopy.c @@ -6,6 +6,7 @@ #include "driver.h" #include "inputs.h" +#include "objedit.h" /* `kit objcopy` — copy + transform an object file. v1 scope is the * high-traffic build-system subset called out in CTOOLCHAIN.md: @@ -121,15 +122,6 @@ typedef struct CopyOpts { const char* output; } CopyOpts; -static int name_in_list(KitSlice name, const char* const* list, uint32_t n) { - uint32_t i; - if (!name.len) return 0; - for (i = 0; i < n; ++i) { - if (list[i] && kit_slice_eq_cstr(name, list[i])) return 1; - } - return 0; -} - static int push_str(DriverEnv* env, const char*** arr, uint32_t* n, uint32_t* cap, const char* s) { if (*n >= *cap) { @@ -270,54 +262,9 @@ static int apply_strip_pass(DriverEnv* env, KitObjFile* of, KitObjBuilder* b, if (!filter_syms) return 0; /* Collect reloc-targeted sym ids, skipping relocs in debug sections. */ - { - KitObjRelocIter* rit = NULL; - if (kit_obj_reliter_new(of, &rit) != KIT_OK) { - driver_errf(OBJCOPY_TOOL, "out of memory"); - return 1; - } - for (;;) { - KitObjReloc r; - KitIterResult ir = kit_obj_reliter_next(rit, &r); - uint32_t k; - int seen = 0; - if (ir != KIT_ITER_ITEM) break; - if (r.sym == KIT_OBJ_SYMBOL_NONE) continue; - if (r.section != KIT_SECTION_NONE) { - KitObjSecInfo hi; - if (kit_obj_section(of, r.section, &hi) == KIT_OK && - hi.kind == KIT_SEC_DEBUG) { - continue; - } - } - for (k = 0; k < nneeded; ++k) { - if (needed[k] == r.sym) { - seen = 1; - break; - } - } - if (seen) continue; - if (nneeded >= cap_needed) { - uint32_t newcap = cap_needed ? cap_needed * 2u : 32u; - KitObjSymbol* nb = (KitObjSymbol*)driver_alloc_zeroed( - env, (size_t)newcap * sizeof(*nb)); - if (!nb) { - kit_obj_reliter_free(rit); - if (needed) - driver_free(env, needed, (size_t)cap_needed * sizeof(*needed)); - driver_errf(OBJCOPY_TOOL, "out of memory"); - return 1; - } - if (needed) { - memcpy(nb, needed, (size_t)nneeded * sizeof(*needed)); - driver_free(env, needed, (size_t)cap_needed * sizeof(*needed)); - } - needed = nb; - cap_needed = newcap; - } - needed[nneeded++] = r.sym; - } - kit_obj_reliter_free(rit); + if (driver_obj_collect_reloc_target_syms(env, OBJCOPY_TOOL, of, &needed, + &nneeded, &cap_needed) != 0) { + return 1; } /* Walk syms and drop unneeded ones. */ @@ -328,17 +275,10 @@ static int apply_strip_pass(DriverEnv* env, KitObjFile* of, KitObjBuilder* b, for (;;) { KitObjSymInfo si; KitIterResult ir = kit_obj_symiter_next(sit, &si); - uint32_t k; - int in_needed = 0; if (ir != KIT_ITER_ITEM) break; if (si.kind == KIT_SK_UNDEF) continue; - for (k = 0; k < nneeded; ++k) { - if (needed[k] == si.id) { - in_needed = 1; - break; - } - } - if (!in_needed) kit_obj_builder_remove_symbol(b, si.id); + if (!driver_obj_id_in_set(si.id, needed, nneeded)) + kit_obj_builder_remove_symbol(b, si.id); } kit_obj_symiter_free(sit); rc = 0; @@ -358,7 +298,7 @@ static void apply_only_sections(KitObjFile* of, KitObjBuilder* b, for (i = 0; i < n; ++i) { KitObjSecInfo si; if (kit_obj_section(of, i, &si) != KIT_OK) continue; - if (!name_in_list(si.name, opts->only_sections, opts->nonly)) { + if (!driver_name_in_list(si.name, opts->only_sections, opts->nonly)) { kit_obj_builder_remove_section(b, i); } } diff --git a/driver/cmd/objdump.c b/driver/cmd/objdump.c @@ -1222,13 +1222,14 @@ static void dump_dynrelocs(KitObjFile* f) { driver_printf(any ? "\n" : "DYNAMIC RELOCATION RECORDS (none)\n\n"); } -static const char* seg_perms_str(uint32_t perms) { - static char b[4]; - b[0] = (perms & KIT_SEG_R) ? 'r' : '-'; - b[1] = (perms & KIT_SEG_W) ? 'w' : '-'; - b[2] = (perms & KIT_SEG_X) ? 'x' : '-'; - b[3] = '\0'; - return b; +/* Format `perms` into the caller-supplied `buf[4]` and return it. The caller + * owns the storage, so there is no shared mutable state between calls. */ +static const char* seg_perms_str(uint32_t perms, char buf[4]) { + buf[0] = (perms & KIT_SEG_R) ? 'r' : '-'; + buf[1] = (perms & KIT_SEG_W) ? 'w' : '-'; + buf[2] = (perms & KIT_SEG_X) ? 'x' : '-'; + buf[3] = '\0'; + return buf; } /* align is a power of two; report it as 2**N like GNU objdump. */ @@ -1281,6 +1282,7 @@ static void dump_private(KitObjFile* f, const char* label) { if (kit_obj_segiter_new(f, &sit) == KIT_OK) { int any = 0; while (kit_obj_segiter_next(sit, &seg) == KIT_ITER_ITEM) { + char perms[4]; any = 1; driver_printf( " %-12.*s off 0x%016llx vaddr 0x%016llx align 2**%u\n" @@ -1288,7 +1290,7 @@ static void dump_private(KitObjFile* f, const char* label) { KIT_SLICE_ARG(seg.name.len ? seg.name : KIT_SLICE_LIT("LOAD")), (unsigned long long)seg.file_off, (unsigned long long)seg.vaddr, u32_log2(seg.align), (unsigned long long)seg.file_size, - (unsigned long long)seg.vsize, seg_perms_str(seg.perms)); + (unsigned long long)seg.vsize, seg_perms_str(seg.perms, perms)); } kit_obj_segiter_free(sit); if (!any) driver_printf(" (none)\n"); diff --git a/driver/cmd/ranlib.c b/driver/cmd/ranlib.c @@ -49,17 +49,6 @@ void driver_help_ranlib(void) { "usage\n"))); } -static uint64_t ranlib_epoch_from_env(void) { - const char* s = driver_getenv("SOURCE_DATE_EPOCH"); - uint64_t v = 0; - if (!s || !*s) return 0; - for (; *s; ++s) { - if (*s < '0' || *s > '9') return 0; - v = v * 10 + (uint64_t)(*s - '0'); - } - return v; -} - int driver_ranlib(int argc, char** argv) { DriverEnv env; KitContext ctx; @@ -130,7 +119,7 @@ int driver_ranlib(int argc, char** argv) { KIT_SLICE_ARG(kit_slice_cstr(archive_path))); goto out; } - opts.epoch = ranlib_epoch_from_env(); + opts.epoch = driver_epoch_from_env(); opts.long_names = 1; opts.symbol_index = 1; rc = kit_ar_write(out, NULL, 0, &opts) == KIT_OK ? 0 : 1; @@ -211,7 +200,7 @@ int driver_ranlib(int argc, char** argv) { KIT_SLICE_ARG(kit_slice_cstr(archive_path))); goto out; } - opts.epoch = ranlib_epoch_from_env(); + opts.epoch = driver_epoch_from_env(); opts.long_names = 1; opts.symbol_index = 1; opts.member_symbols = msyms; diff --git a/driver/cmd/run.c b/driver/cmd/run.c @@ -1045,6 +1045,20 @@ int driver_run(int argc, char** argv) { return rc; } + /* Compile/JIT succeeded; enforce -Werror / note -fmax-errors before running + * the entry. On a -Werror trip we tear the JIT image down, never executing. */ + if (driver_diag_finish(&env, RUN_TOOL, ro.warnings_are_errors, + ro.max_errors)) { + kit_interp_program_free(interp); + kit_jit_free(jit); + driver_compiler_free(compiler); + kit_target_free(target); + run_metrics_finish(metrics); + run_options_release(&ro); + driver_env_fini(&env); + return 1; + } + run_metrics_begin(metrics, "run.jit_lookup"); sym = kit_jit_lookup(jit, kit_slice_cstr(ro.entry)); run_metrics_end(metrics, "run.jit_lookup"); diff --git a/driver/cmd/strip.c b/driver/cmd/strip.c @@ -6,6 +6,7 @@ #include "driver.h" #include "inputs.h" +#include "objedit.h" /* `kit strip` — drop debug sections and / or unwanted symbols from a * relocatable object or static archive, then write the result back. Scope @@ -79,15 +80,6 @@ typedef struct StripOpts { const char* input; } StripOpts; -static int name_in_list(KitSlice name, const char* const* list, uint32_t n) { - uint32_t i; - if (!name.len) return 0; - for (i = 0; i < n; ++i) { - if (list[i] && kit_slice_eq_cstr(name, list[i])) return 1; - } - return 0; -} - static int push_name(DriverEnv* env, const char*** arr, uint32_t* n, uint32_t* cap, const char* name) { if (*n >= *cap) { @@ -134,80 +126,6 @@ static int parse_name_arg(int* i, int argc, char** argv, const char* flag, return 0; } -/* Collect the set of KitObjSymbol ids targeted by any reloc whose - * containing section will survive emit — relocs inside the - * about-to-be-removed KIT_SEC_DEBUG sections don't count. Otherwise a - * symbol that's referenced only from DWARF (e.g. main's debug_info entry) - * keeps every function symbol alive even though the on-disk relocs - * holding it won't make it to the output. */ -static int collect_needed_syms(DriverEnv* env, KitObjFile* of, - KitObjSymbol** needed_out, uint32_t* n_out, - uint32_t* cap_out) { - KitObjRelocIter* rit = NULL; - KitObjSymbol* arr = NULL; - uint32_t n = 0, cap = 0; - - if (kit_obj_reliter_new(of, &rit) != KIT_OK) { - driver_errf(STRIP_TOOL, "out of memory"); - return 1; - } - for (;;) { - KitObjReloc r; - KitIterResult ir = kit_obj_reliter_next(rit, &r); - uint32_t k; - int seen = 0; - if (ir != KIT_ITER_ITEM) break; - if (r.sym == KIT_OBJ_SYMBOL_NONE) continue; - /* Skip relocs hosted in a debug section — that section is being - * dropped, so its relocs don't actually "need" their targets. */ - if (r.section != KIT_SECTION_NONE) { - KitObjSecInfo hi; - if (kit_obj_section(of, r.section, &hi) == KIT_OK && - hi.kind == KIT_SEC_DEBUG) { - continue; - } - } - for (k = 0; k < n; ++k) { - if (arr[k] == r.sym) { - seen = 1; - break; - } - } - if (seen) continue; - if (n >= cap) { - uint32_t newcap = cap ? cap * 2u : 32u; - KitObjSymbol* nb = - (KitObjSymbol*)driver_alloc_zeroed(env, (size_t)newcap * sizeof(*nb)); - if (!nb) { - kit_obj_reliter_free(rit); - if (arr) driver_free(env, arr, (size_t)cap * sizeof(*arr)); - driver_errf(STRIP_TOOL, "out of memory"); - return 1; - } - if (arr) { - memcpy(nb, arr, (size_t)n * sizeof(*arr)); - driver_free(env, arr, (size_t)cap * sizeof(*arr)); - } - arr = nb; - cap = newcap; - } - arr[n++] = r.sym; - } - kit_obj_reliter_free(rit); - *needed_out = arr; - *n_out = n; - *cap_out = cap; - return 0; -} - -static int id_in_set(KitObjSymbol id, const KitObjSymbol* arr, uint32_t n) { - uint32_t i; - for (i = 0; i < n; ++i) { - if (arr[i] == id) return 1; - } - return 0; -} - /* The core strip pass: drop debug sections, then walk symbols and apply * keep/strip lists and the operation policy. Mutations are issued * against the builder; emit-time sweep cleans up cascades (orphan @@ -233,7 +151,8 @@ static int strip_one_builder(DriverEnv* env, KitObjFile* of, KitObjBuilder* b, /* Step 2: compute the needed-sym set. */ if (filter_syms) { - if (collect_needed_syms(env, of, &needed, &nneeded, &cap_needed) != 0) { + if (driver_obj_collect_reloc_target_syms(env, STRIP_TOOL, of, &needed, + &nneeded, &cap_needed) != 0) { return 1; } } @@ -249,9 +168,11 @@ static int strip_one_builder(DriverEnv* env, KitObjFile* of, KitObjBuilder* b, int drop = 0; if (ir != KIT_ITER_ITEM) break; /* --strip-symbol wins over --keep-symbol if both list the same name. */ - if (opts->nstrip && name_in_list(si.name, opts->strip, opts->nstrip)) { + if (opts->nstrip && + driver_name_in_list(si.name, opts->strip, opts->nstrip)) { drop = 1; - } else if (opts->nkeep && name_in_list(si.name, opts->keep, opts->nkeep)) { + } else if (opts->nkeep && + driver_name_in_list(si.name, opts->keep, opts->nkeep)) { drop = 0; } else if (filter_syms) { /* Keep undefined externals so the .o stays linkable; keep symbols @@ -261,7 +182,7 @@ static int strip_one_builder(DriverEnv* env, KitObjFile* of, KitObjBuilder* b, * needed here. */ if (si.kind == KIT_SK_UNDEF) { drop = 0; - } else if (id_in_set(si.id, needed, nneeded)) { + } else if (driver_obj_id_in_set(si.id, needed, nneeded)) { drop = 0; } else { drop = 1; @@ -338,17 +259,6 @@ static int strip_object_bytes(DriverEnv* env, const KitContext* ctx, return rc; } -static uint64_t strip_epoch_from_env(void) { - const char* s = driver_getenv("SOURCE_DATE_EPOCH"); - uint64_t v = 0; - if (!s || !*s) return 0; - for (; *s; ++s) { - if (*s < '0' || *s > '9') return 0; - v = v * 10 + (uint64_t)(*s - '0'); - } - return v; -} - /* Strip every object member of an archive, write a fresh archive with * a refreshed System-V symbol index. Non-object members pass through * unchanged. */ @@ -487,7 +397,7 @@ static int strip_archive(DriverEnv* env, const KitContext* ctx, KIT_SLICE_ARG(kit_slice_cstr(output_path))); goto done; } - opts_ar.epoch = strip_epoch_from_env(); + opts_ar.epoch = driver_epoch_from_env(); opts_ar.long_names = 1; opts_ar.symbol_index = 1; opts_ar.member_symbols = msyms; diff --git a/driver/cmd/xxd.c b/driver/cmd/xxd.c @@ -126,38 +126,6 @@ static int xxd_is_hex(int c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } -static int xxd_hexval(int c) { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - return c - 'A' + 10; -} - -/* Parse a decimal or 0x-hex non-negative integer. Returns 0 on success. */ -static int xxd_parse_u64(const char* s, uint64_t* out) { - uint64_t v = 0; - int base = 10; - if (!s || !*s) return 1; - if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { - base = 16; - s += 2; - if (!*s) return 1; - } - for (; *s; ++s) { - unsigned d; - char c = *s; - if (c >= '0' && c <= '9') - d = (unsigned)(c - '0'); - else if (base == 16 && (c >= 'a' && c <= 'f')) - d = (unsigned)(c - 'a' + 10); - else if (base == 16 && (c >= 'A' && c <= 'F')) - d = (unsigned)(c - 'A' + 10); - else - return 1; - v = v * (uint64_t)base + d; - } - *out = v; - return 0; -} /* Derive a C identifier from a file path: basename, non-alnum -> '_', a leading * digit gets an '_' prefix. Writes into out (cap bytes). */ @@ -266,9 +234,9 @@ static void xxd_reverse(Xb* b, const uint8_t* data, size_t len, int plain) { } if (xxd_is_hex(ch)) { if (hi < 0) { - hi = xxd_hexval(ch); + hi = driver_hex_nibble((char)ch); } else { - xb_c(b, (char)((hi << 4) | xxd_hexval(ch))); + xb_c(b, (char)((hi << 4) | driver_hex_nibble((char)ch))); hi = -1; } continue; @@ -335,7 +303,7 @@ int driver_xxd(int argc, char** argv) { if (a[0] == '-' && a[1] == 'c') { const char* v = xxd_optval(a, argc, argv, &i); uint64_t n; - if (!v || xxd_parse_u64(v, &n) != 0 || n == 0) { + if (!v || driver_parse_u64(v, &n) != 0 || n == 0) { driver_errf(XXD_TOOL, "-c requires a positive integer"); goto done; } @@ -345,7 +313,7 @@ int driver_xxd(int argc, char** argv) { if (a[0] == '-' && a[1] == 'g') { const char* v = xxd_optval(a, argc, argv, &i); uint64_t n; - if (!v || xxd_parse_u64(v, &n) != 0 || n == 0) { + if (!v || driver_parse_u64(v, &n) != 0 || n == 0) { driver_errf(XXD_TOOL, "-g requires a positive integer"); goto done; } @@ -354,7 +322,7 @@ int driver_xxd(int argc, char** argv) { } if (a[0] == '-' && a[1] == 's') { const char* v = xxd_optval(a, argc, argv, &i); - if (!v || xxd_parse_u64(v, &o.seek) != 0) { + if (!v || driver_parse_u64(v, &o.seek) != 0) { driver_errf(XXD_TOOL, "-s requires a byte offset"); goto done; } @@ -362,7 +330,7 @@ int driver_xxd(int argc, char** argv) { } if (a[0] == '-' && a[1] == 'l') { const char* v = xxd_optval(a, argc, argv, &i); - if (!v || xxd_parse_u64(v, &o.limit) != 0) { + if (!v || driver_parse_u64(v, &o.limit) != 0) { driver_errf(XXD_TOOL, "-l requires a length"); goto done; } diff --git a/driver/driver.h b/driver/driver.h @@ -15,23 +15,6 @@ * * Preprocessor-only mode is `cc -E` — there is no separate `cpp` tool. */ -typedef enum DriverTool { - DRIVER_TOOL_CC, - DRIVER_TOOL_CHECK, - DRIVER_TOOL_CPP, - DRIVER_TOOL_AS, - DRIVER_TOOL_LD, - DRIVER_TOOL_AR, - DRIVER_TOOL_RANLIB, - DRIVER_TOOL_OBJDUMP, - DRIVER_TOOL_DBG, - DRIVER_TOOL_RUN, - DRIVER_TOOL_EMU, - DRIVER_TOOL_NM, - DRIVER_TOOL_SIZE, - DRIVER_TOOL_ADDR2LINE, -} DriverTool; - /* Multi-call entry: dispatches by argv[0] basename (or argv[1] fallback). */ int driver_main(int argc, char** argv); @@ -177,6 +160,36 @@ int driver_arch_from_name(const char* name, KitArchKind* arch_out, * adding a frontend extension reaches every tool at once. */ int driver_path_is_source(const char* path); +/* ---------------------------------------------------------------------- + * Shared scalar parsing helpers + * + * Small, OS-neutral parsers the byte/binutils tools used to each open-code. + * Implemented in driver/env/common.c. + * ---------------------------------------------------------------------- */ + +/* Parse SOURCE_DATE_EPOCH into a u64 seconds count. Returns 0 when the variable + * is unset, empty, or not all decimal digits ("no epoch"). */ +uint64_t driver_epoch_from_env(void); + +/* Parse a non-negative integer that is either decimal or `0x`/`0X`-prefixed + * hex. Rejects empty input, a bare `0x` with no digits, and any non-digit. + * Returns 0 on success (value in *out), nonzero on a malformed string. */ +int driver_parse_u64(const char* s, uint64_t* out); + +/* Map one hex digit (0-9, a-f, A-F) to its 0..15 value, or -1 if `c` is not a + * hex digit. */ +int driver_hex_nibble(char c); + +/* Join two path components into a freshly heap-allocated, NUL-terminated string + * `<a>/<b>`. A '/' separator is inserted iff `a` is non-empty and does not + * already end in '/' or '\\' (so Windows-style separators are not doubled). An + * empty `a` yields just `<b>` (relative to the current directory); a NULL/empty + * `b` yields a copy of `a`. When `out_size` is non-NULL it receives the + * allocation size (length + 1) for a later driver_free. Returns NULL only on + * allocation failure. */ +char* driver_path_join(DriverEnv* env, const char* a, const char* b, + size_t* out_size); + typedef struct DriverTargetFeatures { DriverEnv* env; KitTargetFeature* features; diff --git a/driver/env.h b/driver/env.h @@ -59,6 +59,16 @@ KitStatus driver_compiler_new(const KitTarget*, const KitContext*, KitCompiler** out); void driver_compiler_free(KitCompiler*); +/* Driver-level post-compile diagnostic gate, enforcing the warning policies the + * C frontend does not yet honor from KitDiagnosticOptions. Call once after a + * compile/link run completes. When `warnings_are_errors` is set and libkit's + * diag sink recorded any warnings, emits an error and returns nonzero so the + * caller can fail the tool (-Werror). When `max_errors` is nonzero, emits a + * single note that -fmax-errors is unimplemented (the frontend does not bound + * the error count). Returns 0 when the run should be considered successful. */ +int driver_diag_finish(DriverEnv* env, const char* tool, + int warnings_are_errors, uint32_t max_errors); + /* Default target used by tools that don't expose a target-selection flag * yet. v1: native-looking host target (chosen at compile time). */ KitTargetSpec driver_host_target(void); diff --git a/driver/env/common.c b/driver/env/common.c @@ -55,12 +55,12 @@ static const char* diag_label(KitDiagKind k) { return "diag"; } -/* Tracks the compiler currently driving libkit calls so the stderr - * diag sink can resolve loc.file_id to the source's spelling (path or - * memory-input label). NULL falls back to the numeric `<file:%u>` form. */ -static KitCompiler* g_diag_active_compiler; - -void driver_diag_set_compiler(KitCompiler* c) { g_diag_active_compiler = c; } +/* The compiler currently driving libkit calls is stashed in the stderr sink's + * `user` field so the sink can resolve loc.file_id to the source's spelling + * (path or memory-input label). NULL falls back to the numeric `<file:%u>` + * form. Held on the sink rather than in process-global state so the pointer + * travels with the sink the emit callback is invoked on. */ +void driver_diag_set_compiler(KitCompiler* c) { g_diag_stderr.user = c; } KitStatus driver_compiler_new(const KitTarget* t, const KitContext* ctx, KitCompiler** out) { @@ -77,15 +77,15 @@ KitStatus driver_compiler_new(const KitTarget* t, const KitContext* ctx, void driver_compiler_free(KitCompiler* c) { if (!c) return; - if (g_diag_active_compiler == c) driver_diag_set_compiler(NULL); + if (g_diag_stderr.user == c) driver_diag_set_compiler(NULL); kit_compiler_free(c); } static void diag_stderr_emit(KitDiagSink* s, KitDiagKind k, KitSrcLoc loc, const char* fmt, va_list ap) { - (void)s; + KitCompiler* compiler = s ? (KitCompiler*)s->user : NULL; if (loc.file_id || loc.line) { - KitSlice name = kit_compiler_file_name(g_diag_active_compiler, loc.file_id); + KitSlice name = kit_compiler_file_name(compiler, loc.file_id); if (name.len) { fprintf(stderr, "%.*s:%u:%u: %.*s: ", KIT_SLICE_ARG(name), loc.line, loc.col, KIT_SLICE_ARG(kit_slice_cstr(diag_label(k)))); @@ -107,6 +107,31 @@ KitDiagSink g_diag_stderr = { 0, }; +/* Driver-level post-compile diagnostic gate. The C frontend currently ignores + * KitDiagnosticOptions, so the two warning policies are enforced here, over the + * counts libkit maintains on the diag sink: + * -Werror : a successful compile with any emitted warning becomes a + * failure (the frontend kept compiling; we fail the tool). + * -fmax-errors : not yet honored mid-compile by the frontend, so rather than + * silently accept it we emit a single explanatory note. + * Returns nonzero when the run should be treated as failed. */ +int driver_diag_finish(DriverEnv* env, const char* tool, + int warnings_are_errors, uint32_t max_errors) { + KitDiagSink* sink = env ? env->diag : NULL; + uint32_t warnings = sink ? sink->warnings : 0u; + if (max_errors) { + driver_errf(tool, + "note: -fmax-errors is unimplemented; it does not bound the " + "error count"); + } + if (warnings_are_errors && warnings) { + driver_errf(tool, "%u warning%s treated as error%s (-Werror)", warnings, + warnings == 1u ? "" : "s", warnings == 1u ? "" : "s"); + return 1; + } + return 0; +} + /* ---------------- alloc helpers ---------------- */ void* driver_alloc(DriverEnv* e, size_t n) { @@ -127,31 +152,52 @@ void driver_memcpy(void* dst, const void* src, size_t n) { memcpy(dst, src, n); } +/* ---------------- file load/release (OS-neutral) ---------------- */ + +/* Pure file_io vtable bookkeeping: no per-host behavior, so it lives here + * rather than being duplicated in each env/<host>.c. */ + +int driver_load_bytes(const KitFileIO* io, const char* tool, const char* path, + DriverLoad* out, KitSlice* in) { + out->loaded = 0; + out->fd.data = NULL; + out->fd.size = 0; + out->fd.token = NULL; + if (!io || !io->read_all) { + driver_errf(tool, "host file I/O unavailable"); + return 1; + } + if (io->read_all(io->user, path, &out->fd) != KIT_OK) { + driver_errf(tool, "failed to read: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(path))); + return 1; + } + out->loaded = 1; + in->data = out->fd.data; + in->len = out->fd.size; + return 0; +} + +void driver_release_bytes(const KitFileIO* io, DriverLoad* lf) { + if (!lf || !lf->loaded) return; + if (io && io->release) io->release(io->user, &lf->fd); + lf->loaded = 0; +} + /* ---------------- hosted dir lists ---------------- */ -/* Mechanical only: join `a` (+ optional `/sub`), dup into the list. No per-OS - * policy lives here -- producers (driver/lib/hosted.c expansion and the - * per-host driver_default_hosted_dirs probes) supply the paths. */ +/* Mechanical only: join `a` (+ optional `/sub`) via driver_path_join and dup + * into the list. No per-OS policy lives here -- producers (driver/lib/hosted.c + * expansion and the per-host driver_default_hosted_dirs probes) supply the + * paths. */ static int hd_store(DriverEnv* env, char** arr, size_t* sizes, uint32_t* n, uint32_t cap, const char* a, const char* b) { - size_t alen, blen, slash, bytes, off; + size_t bytes; char* out; if (!a || !a[0]) return 0; /* empty base -> successful no-op */ if (*n >= cap) return 1; /* loud overflow -- never a silent drop */ - alen = driver_strlen(a); - blen = b ? driver_strlen(b) : 0; - slash = (blen && a[alen - 1] != '/') ? 1u : 0u; - bytes = alen + slash + blen + 1u; - out = driver_alloc(env, bytes); + out = driver_path_join(env, a, b, &bytes); if (!out) return 1; - driver_memcpy(out, a, alen); - off = alen; - if (slash) out[off++] = '/'; - if (blen) { - driver_memcpy(out + off, b, blen); - off += blen; - } - out[off] = '\0'; arr[*n] = out; sizes[*n] = bytes; (*n)++; @@ -221,7 +267,9 @@ const char* driver_strchr(const char* s, int c) { const char* driver_basename(const char* path) { size_t i = kit_slice_cstr(path).len; while (i > 0) { - if (path[i - 1] == '/') return path + i; + /* Accept both separators so a Windows argv[0] like "C:\\bin\\kit.exe" + * strips to its basename rather than returning the full path. */ + if (path[i - 1] == '/' || path[i - 1] == '\\') return path + i; --i; } return path; @@ -233,6 +281,71 @@ int driver_has_suffix(const char* s, const char* suffix) { return ls >= lf && memcmp(s + ls - lf, suffix, lf) == 0; } +/* ---------------- scalar parsing helpers ---------------- */ + +int driver_hex_nibble(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return 10 + (c - 'a'); + if (c >= 'A' && c <= 'F') return 10 + (c - 'A'); + return -1; +} + +uint64_t driver_epoch_from_env(void) { + const char* s = driver_getenv("SOURCE_DATE_EPOCH"); + uint64_t v = 0; + if (!s || !*s) return 0; + for (; *s; ++s) { + if (*s < '0' || *s > '9') return 0; + v = v * 10 + (uint64_t)(*s - '0'); + } + return v; +} + +int driver_parse_u64(const char* s, uint64_t* out) { + uint64_t v = 0; + int base = 10; + if (!s || !*s) return 1; + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + base = 16; + s += 2; + if (!*s) return 1; + } + for (; *s; ++s) { + int d = driver_hex_nibble(*s); + if (d < 0 || d >= base) return 1; + v = v * (uint64_t)base + (uint64_t)d; + } + *out = v; + return 0; +} + +char* driver_path_join(DriverEnv* env, const char* a, const char* b, + size_t* out_size) { + size_t al = a ? driver_strlen(a) : 0u; + size_t bl = b ? driver_strlen(b) : 0u; + /* Insert a '/' only when there is a `b` to separate from `a` and `a` does + * not already end in a separator -- a NULL/empty `b` yields a plain copy of + * `a` with no trailing slash. */ + size_t slash = + (bl && al > 0 && a[al - 1u] != '/' && a[al - 1u] != '\\') ? 1u : 0u; + size_t bytes = al + slash + bl + 1u; + char* out = (char*)driver_alloc(env, bytes); + size_t off = 0; + if (!out) return NULL; + if (al) { + driver_memcpy(out, a, al); + off = al; + } + if (slash) out[off++] = '/'; + if (bl) { + driver_memcpy(out + off, b, bl); + off += bl; + } + out[off] = '\0'; + if (out_size) *out_size = bytes; + return out; +} + /* ---------------- printf/errf/logf ---------------- */ void driver_errf(const char* tool, const char* fmt, ...) { diff --git a/driver/env/posix.c b/driver/env/posix.c @@ -584,19 +584,6 @@ int driver_path_lexists(const char* path) { return lstat(path, &sb) == 0; } -static char* driver_join_path(DriverEnv* env, const char* a, const char* b) { - size_t al = kit_slice_cstr(a).len; - size_t bl = kit_slice_cstr(b).len; - int slash = al > 0 && a[al - 1u] != '/'; - char* out = (char*)driver_alloc(env, al + (slash ? 1u : 0u) + bl + 1u); - if (!out) return NULL; - memcpy(out, a, al); - if (slash) out[al++] = '/'; - memcpy(out + al, b, bl); - out[al + bl] = '\0'; - return out; -} - static int driver_walk_regular_files_at(DriverEnv* env, const char* dir, const char* rel, DriverWalkFileFn cb, void* user) { @@ -612,10 +599,10 @@ static int driver_walk_regular_files_at(DriverEnv* env, const char* dir, struct stat sb; int child_rc = 0; if (driver_streq(name, ".") || driver_streq(name, "..")) continue; - child = driver_join_path(env, dir, name); + child = driver_path_join(env, dir, name, NULL); if (!child) goto out; - child_rel = rel && rel[0] ? driver_join_path(env, rel, name) - : driver_join_path(env, "", name); + child_rel = rel && rel[0] ? driver_path_join(env, rel, name, NULL) + : driver_path_join(env, "", name, NULL); if (!child_rel) { driver_free(env, child, kit_slice_cstr(child).len + 1u); goto out; @@ -681,32 +668,7 @@ int driver_random_bytes(uint8_t* out, size_t n) { /* ---------------- load helpers ---------------- */ -int driver_load_bytes(const KitFileIO* io, const char* tool, const char* path, - DriverLoad* out, KitSlice* in) { - out->loaded = 0; - out->fd.data = NULL; - out->fd.size = 0; - out->fd.token = NULL; - if (!io || !io->read_all) { - driver_errf(tool, "host file I/O unavailable"); - return 1; - } - if (io->read_all(io->user, path, &out->fd) != KIT_OK) { - driver_errf(tool, "failed to read: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(path))); - return 1; - } - out->loaded = 1; - in->data = out->fd.data; - in->len = out->fd.size; - return 0; -} - -void driver_release_bytes(const KitFileIO* io, DriverLoad* lf) { - if (!lf || !lf->loaded) return; - if (io && io->release) io->release(io->user, &lf->fd); - lf->loaded = 0; -} +/* driver_load_bytes / driver_release_bytes are OS-neutral; see env/common.c. */ /* ---------------- stdin / edit_temp / read_line ---------------- */ diff --git a/driver/env/windows.c b/driver/env/windows.c @@ -590,8 +590,6 @@ struct DriverDirHandle { uint64_t count; }; -static char* driver_join_path(DriverEnv* env, const char* a, const char* b); - DriverDirHandle* driver_open_dir(DriverEnv* env, const char* path) { char* pattern; wchar_t* wpattern; @@ -603,7 +601,7 @@ DriverDirHandle* driver_open_dir(DriverEnv* env, const char* path) { uint64_t count = 0; if (!env || !path) return NULL; - pattern = driver_join_path(env, path, "*"); + pattern = driver_path_join(env, path, "*", NULL); if (!pattern) return NULL; wpattern = widen(pattern); driver_free(env, pattern, kit_slice_cstr(pattern).len + 1u); @@ -908,19 +906,6 @@ int driver_path_lexists(const char* path) { return attr != INVALID_FILE_ATTRIBUTES; } -static char* driver_join_path(DriverEnv* env, const char* a, const char* b) { - size_t al = kit_slice_cstr(a).len; - size_t bl = kit_slice_cstr(b).len; - int slash = al > 0 && a[al - 1u] != '/' && a[al - 1u] != '\\'; - char* out = (char*)driver_alloc(env, al + (slash ? 1u : 0u) + bl + 1u); - if (!out) return NULL; - memcpy(out, a, al); - if (slash) out[al++] = '/'; - memcpy(out + al, b, bl); - out[al + bl] = '\0'; - return out; -} - static int driver_walk_regular_files_at(DriverEnv* env, const char* dir, const char* rel, DriverWalkFileFn cb, void* user) { @@ -931,7 +916,7 @@ static int driver_walk_regular_files_at(DriverEnv* env, const char* dir, DWORD last; int rc = 1; - pattern = driver_join_path(env, dir, "*"); + pattern = driver_path_join(env, dir, "*", NULL); if (!pattern) return 1; wpattern = widen(pattern); driver_free(env, pattern, kit_slice_cstr(pattern).len + 1u); @@ -954,9 +939,9 @@ static int driver_walk_regular_files_at(DriverEnv* env, const char* dir, free(name); goto loop_next; } - child = driver_join_path(env, dir, name); - child_rel = rel && rel[0] ? driver_join_path(env, rel, name) - : driver_join_path(env, "", name); + child = driver_path_join(env, dir, name, NULL); + child_rel = rel && rel[0] ? driver_path_join(env, rel, name, NULL) + : driver_path_join(env, "", name, NULL); if (!child || !child_rel) goto loop_fail; if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { if ((fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { @@ -1039,32 +1024,7 @@ int driver_random_bytes(uint8_t* out, size_t n) { * load helpers * ============================================================ */ -int driver_load_bytes(const KitFileIO* io, const char* tool, const char* path, - DriverLoad* out, KitSlice* in) { - out->loaded = 0; - out->fd.data = NULL; - out->fd.size = 0; - out->fd.token = NULL; - if (!io || !io->read_all) { - driver_errf(tool, "host file I/O unavailable"); - return 1; - } - if (io->read_all(io->user, path, &out->fd) != KIT_OK) { - driver_errf(tool, "failed to read: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(path))); - return 1; - } - out->loaded = 1; - in->data = out->fd.data; - in->len = out->fd.size; - return 0; -} - -void driver_release_bytes(const KitFileIO* io, DriverLoad* lf) { - if (!lf || !lf->loaded) return; - if (io && io->release) io->release(io->user, &lf->fd); - lf->loaded = 0; -} +/* driver_load_bytes / driver_release_bytes are OS-neutral; see env/common.c. */ /* ============================================================ * stdin / edit_temp / read_line diff --git a/driver/lib/hosted.c b/driver/lib/hosted.c @@ -4,29 +4,6 @@ #include <stdint.h> #include <string.h> -static char* hosted_join2(DriverEnv* env, const char* a, const char* b, - size_t* out_size) { - size_t alen = driver_strlen(a); - size_t blen = driver_strlen(b); - size_t slash = (alen > 0 && a[alen - 1] != '/') ? 1u : 0u; - size_t bytes = alen + slash + blen + 1u; - char* out = driver_alloc(env, bytes); - size_t off = 0; - if (!out) return NULL; - if (alen) { - driver_memcpy(out + off, a, alen); - off += alen; - } - if (slash) out[off++] = '/'; - if (blen) { - driver_memcpy(out + off, b, blen); - off += blen; - } - out[off] = '\0'; - if (out_size) *out_size = bytes; - return out; -} - static int hosted_add_input(DriverHostedInput* items, uint32_t* n, uint32_t cap, uint8_t kind, char* path, size_t path_size) { DriverHostedInput* it; @@ -50,7 +27,7 @@ static char* hosted_find_in_libdirs(DriverEnv* env, uint32_t i; for (i = 0; i < dirs->nlibdirs; ++i) { size_t size = 0; - char* path = hosted_join2(env, dirs->libdirs[i], file, &size); + char* path = driver_path_join(env, dirs->libdirs[i], file, &size); if (!path) return NULL; if (driver_path_exists(path)) { *out_size = size; @@ -541,6 +518,14 @@ static int hosted_resolve_windows_mingw(const DriverHostedRequest* req, hosted_add_required_search( plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, "libkernel32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || + /* Deliberate second pass over the mingw/ucrt runtime core (a manual + * --start-group/--end-group): these archives are mutually recursive -- + * libucrt pulls libmingwex/libmoldname members which in turn pull more + * libucrt members, and winpthread/mingw32 close the cycle. Because + * archives are scanned in order with lazy member pulls, one pass leaves + * late-discovered undefs unresolved; re-listing the core set lets the + * linker pull the members the first pass missed. This is NOT a verbatim + * duplicate of the list above (the system import libs are not repeated). */ hosted_add_required_search( plan->after, &plan->nafter, DRIVER_HOSTED_MAX_AFTER, req, dirs, "libmingw32.a", DRIVER_HOSTED_INPUT_ARCHIVE) != 0 || diff --git a/driver/lib/lib_resolve.c b/driver/lib/lib_resolve.c @@ -4,45 +4,38 @@ #include <stdint.h> /* Compose `<dir>/<prefix><name><suffix>` into a fresh heap buffer. - * Inserts a separating '/' iff `dir` does not already end in one. * Empty `dir` is treated as the current directory: the path becomes * `<prefix><name><suffix>`. `prefix` is "lib" or "" (Windows MSVC- * style libs ship without the prefix); `suffix` is e.g. ".a" or ".so" - * — both caller-owned, NUL-terminated. */ + * — both caller-owned, NUL-terminated. The leaf (`prefix+name+suffix`) is + * built first, then joined onto `dir` via the shared driver_path_join so the + * separator decision (including Windows '\\') lives in one place. */ static char* compose_path(DriverEnv* env, const char* dir, const char* prefix, const char* name, const char* suffix, size_t* out_size) { - size_t dlen = driver_strlen(dir); size_t plen = driver_strlen(prefix); size_t nlen = driver_strlen(name); size_t slen = driver_strlen(suffix); - size_t need_slash = (dlen > 0 && dir[dlen - 1] != '/') ? 1 : 0; - /* "<dir>" + "/"? + "<prefix>" + "<name>" + "<suffix>" + NUL */ - size_t bytes = dlen + need_slash + plen + nlen + slen + 1; - char* buf = driver_alloc(env, bytes); + size_t leaf_bytes = plen + nlen + slen + 1; + char* leaf = driver_alloc(env, leaf_bytes); + char* buf; size_t off = 0; - if (!buf) return NULL; - if (dlen) { - driver_memcpy(buf + off, dir, dlen); - off += dlen; - } - if (need_slash) { - buf[off++] = '/'; - } + if (!leaf) return NULL; if (plen) { - driver_memcpy(buf + off, prefix, plen); + driver_memcpy(leaf + off, prefix, plen); off += plen; } if (nlen) { - driver_memcpy(buf + off, name, nlen); + driver_memcpy(leaf + off, name, nlen); off += nlen; } if (slen) { - driver_memcpy(buf + off, suffix, slen); + driver_memcpy(leaf + off, suffix, slen); off += slen; } - buf[off] = '\0'; - *out_size = bytes; + leaf[off] = '\0'; + buf = driver_path_join(env, dir, leaf, out_size); + driver_free(env, leaf, leaf_bytes); return buf; } diff --git a/driver/lib/link_flags.c b/driver/lib/link_flags.c @@ -94,22 +94,6 @@ static void lf_free_build_id(DriverLinkFlags* lf) { } } -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) { @@ -119,8 +103,9 @@ static int lf_parse_hex_bytes(DriverLinkFlags* lf, const char* s, size_t n, 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) { + int hi = driver_hex_nibble(s[i]); + int lo = driver_hex_nibble(s[i + 1u]); + if (hi < 0 || lo < 0) { driver_free(lf->env, bs, n / 2u); return 1; } diff --git a/driver/lib/objedit.c b/driver/lib/objedit.c @@ -0,0 +1,72 @@ +#include "objedit.h" + +#include <string.h> + +int driver_name_in_list(KitSlice name, const char* const* list, uint32_t n) { + uint32_t i; + if (!name.len) return 0; + for (i = 0; i < n; ++i) { + if (list[i] && kit_slice_eq_cstr(name, list[i])) return 1; + } + return 0; +} + +int driver_obj_id_in_set(KitObjSymbol id, const KitObjSymbol* arr, uint32_t n) { + uint32_t i; + for (i = 0; i < n; ++i) { + if (arr[i] == id) return 1; + } + return 0; +} + +int driver_obj_collect_reloc_target_syms(DriverEnv* env, const char* tool, + KitObjFile* of, KitObjSymbol** out, + uint32_t* n_out, uint32_t* cap_out) { + KitObjRelocIter* rit = NULL; + KitObjSymbol* arr = NULL; + uint32_t n = 0, cap = 0; + + if (kit_obj_reliter_new(of, &rit) != KIT_OK) { + driver_errf(tool, "out of memory"); + return 1; + } + for (;;) { + KitObjReloc r; + KitIterResult ir = kit_obj_reliter_next(rit, &r); + if (ir != KIT_ITER_ITEM) break; + if (r.sym == KIT_OBJ_SYMBOL_NONE) continue; + /* Skip relocs hosted in a debug section -- that section is being + * dropped, so its relocs don't actually "need" their targets. */ + if (r.section != KIT_SECTION_NONE) { + KitObjSecInfo hi; + if (kit_obj_section(of, r.section, &hi) == KIT_OK && + hi.kind == KIT_SEC_DEBUG) { + continue; + } + } + if (driver_obj_id_in_set(r.sym, arr, n)) continue; + if (n >= cap) { + uint32_t newcap = cap ? cap * 2u : 32u; + KitObjSymbol* nb = + (KitObjSymbol*)driver_alloc_zeroed(env, (size_t)newcap * sizeof(*nb)); + if (!nb) { + kit_obj_reliter_free(rit); + if (arr) driver_free(env, arr, (size_t)cap * sizeof(*arr)); + driver_errf(tool, "out of memory"); + return 1; + } + if (arr) { + memcpy(nb, arr, (size_t)n * sizeof(*arr)); + driver_free(env, arr, (size_t)cap * sizeof(*arr)); + } + arr = nb; + cap = newcap; + } + arr[n++] = r.sym; + } + kit_obj_reliter_free(rit); + *out = arr; + *n_out = n; + *cap_out = cap; + return 0; +} diff --git a/driver/lib/objedit.h b/driver/lib/objedit.h @@ -0,0 +1,33 @@ +#ifndef KIT_DRIVER_LIB_OBJEDIT_H +#define KIT_DRIVER_LIB_OBJEDIT_H + +#include <kit/core.h> +#include <kit/object.h> +#include <stdint.h> + +#include "driver.h" + +/* Object-editing helpers shared by the `strip` and `objcopy` tools, which both + * drop debug sections and then prune symbols not reached by a surviving reloc. + * Kept here so the reloc-target scan and the small membership predicates live + * in one place rather than being copied between the two drivers. */ + +/* Whether `name` exactly matches one of the `n` NUL-terminated entries in + * `list`. An empty `name` never matches. */ +int driver_name_in_list(KitSlice name, const char* const* list, uint32_t n); + +/* Whether symbol id `id` appears in `arr[0..n)`. */ +int driver_obj_id_in_set(KitObjSymbol id, const KitObjSymbol* arr, uint32_t n); + +/* Collect (deduplicated) the symbol ids targeted by any relocation whose + * containing section will survive emit -- relocs hosted in KIT_SEC_DEBUG + * sections are skipped, since those sections are about to be dropped and so do + * not actually keep their targets alive. On success *out owns a freshly + * allocated array of *n ids with capacity *cap (free with driver_free over + * *cap * sizeof(KitObjSymbol)); on failure emits an error tagged `tool` and + * returns nonzero. Returns 0 on success. */ +int driver_obj_collect_reloc_target_syms(DriverEnv* env, const char* tool, + KitObjFile* of, KitObjSymbol** out, + uint32_t* n, uint32_t* cap); + +#endif diff --git a/driver/lib/runtime.c b/driver/lib/runtime.c @@ -210,25 +210,9 @@ static char* rt_dup(DriverEnv* env, const char* s, size_t* out_size) { return p; } -static char* rt_join(DriverEnv* env, const char* a, const char* b, - size_t* out_size) { - size_t la = driver_strlen(a); - size_t lb = driver_strlen(b); - int slash = la > 0 && a[la - 1u] != '/'; - size_t n = la + (slash ? 1u : 0u) + lb + 1u; - char* p = (char*)driver_alloc(env, n); - if (!p) return NULL; - driver_memcpy(p, a, la); - if (slash) p[la++] = '/'; - driver_memcpy(p + la, b, lb); - p[la + lb] = '\0'; - if (out_size) *out_size = n; - return p; -} - static char* rt_join_slot(DriverEnv* env, char** slot, size_t* size_slot, const char* a, const char* b) { - char* p = rt_join(env, a, b, size_slot); + char* p = driver_path_join(env, a, b, size_slot); *slot = p; return p; } @@ -261,7 +245,8 @@ static int rt_set_layout(DriverEnv* env, DriverRuntimeSupport* out, const char* support_root, const char* rt_root) { out->support_root = rt_dup(env, support_root, &out->support_root_size); out->rt_root = rt_dup(env, rt_root, &out->rt_root_size); - out->include_dir = rt_join(env, rt_root, "include", &out->include_dir_size); + out->include_dir = + driver_path_join(env, rt_root, "include", &out->include_dir_size); if (!out->support_root || !out->rt_root || !out->include_dir) { driver_runtime_support_fini(env, out); return 1; @@ -275,7 +260,7 @@ static int rt_try_support_root(DriverEnv* env, const char* root, size_t rt_root_size = 0; int ok; - rt_root = rt_join(env, root, "rt", &rt_root_size); + rt_root = driver_path_join(env, root, "rt", &rt_root_size); if (!rt_root) return 1; ok = rt_has_layout(rt_root) && rt_set_layout(env, out, root, rt_root) == 0; driver_free(env, rt_root, rt_root_size); @@ -317,7 +302,7 @@ static int rt_try_argv0_support(DriverEnv* env, const char* argv0, driver_memcpy(dir, argv0, dir_len); dir[dir_len] = '\0'; - support = rt_join(env, dir, "support", &support_size); + support = driver_path_join(env, dir, "support", &support_size); driver_free(env, dir, dir_size); if (!support) return 1; rc = rt_try_support_root(env, support, out); @@ -348,7 +333,7 @@ static int rt_try_argv0_checkout_root(DriverEnv* env, const char* argv0, driver_memcpy(dir, argv0, dir_len); dir[dir_len] = '\0'; - parent = rt_join(env, dir, "..", &parent_size); + parent = driver_path_join(env, dir, "..", &parent_size); driver_free(env, dir, dir_size); if (!parent) return 1; rc = rt_try_support_root(env, parent, out); @@ -607,9 +592,9 @@ static int rt_compile_source(DriverEnv* env, int rc = 1; *obj_writer = NULL; - lib_path = rt_join(env, support->rt_root, "lib", &lib_path_size); + lib_path = driver_path_join(env, support->rt_root, "lib", &lib_path_size); if (!lib_path) goto out; - src_path = rt_join(env, lib_path, rel, &src_path_size); + src_path = driver_path_join(env, lib_path, rel, &src_path_size); if (!src_path) goto out; if (driver_load_bytes(ctx.file_io, tool, src_path, &src_load, &input) != 0) goto out; @@ -818,9 +803,10 @@ static int rt_is_archive_stale(DriverEnv* env, size_t src_path_size = 0; int64_t src_mtime; int stale = 0; - lib_path = rt_join(env, support->rt_root, "lib", &lib_path_size); + lib_path = driver_path_join(env, support->rt_root, "lib", &lib_path_size); if (!lib_path) return 1; - src_path = rt_join(env, lib_path, variant->sources[i], &src_path_size); + src_path = + driver_path_join(env, lib_path, variant->sources[i], &src_path_size); driver_free(env, lib_path, lib_path_size); if (!src_path) return 1; if (driver_path_mtime_ns(src_path, &src_mtime) != 0 || @@ -839,9 +825,9 @@ static char* rt_archive_dir_for_root(DriverEnv* env, const char* root, char* dir = NULL; size_t build_rt_size = 0; size_t dir_size = 0; - build_rt = rt_join(env, root, "build/rt", &build_rt_size); + build_rt = driver_path_join(env, root, "build/rt", &build_rt_size); if (!build_rt) return NULL; - dir = rt_join(env, build_rt, variant->key, &dir_size); + dir = driver_path_join(env, build_rt, variant->key, &dir_size); driver_free(env, build_rt, build_rt_size); if (out_size) *out_size = dir ? dir_size : 0; return dir; @@ -855,7 +841,8 @@ static int rt_archive_try_dir(DriverEnv* env, const char* tool, char* archive_path; size_t archive_path_size = 0; - archive_path = rt_join(env, cache_dir, "libkit_rt.a", &archive_path_size); + archive_path = + driver_path_join(env, cache_dir, "libkit_rt.a", &archive_path_size); if (!archive_path) { driver_errf(tool, "out of memory"); return 1; @@ -915,7 +902,8 @@ int driver_runtime_ensure_archive(DriverEnv* env, const char* tool, } /* User cache location, outside any support/install tree. */ if (env->cache_dir) { - cache_dir = rt_join(env, env->cache_dir, variant->key, &cache_dir_size); + cache_dir = + driver_path_join(env, env->cache_dir, variant->key, &cache_dir_size); if (!cache_dir) { driver_free(env, in_tree_dir, in_tree_dir_size); driver_errf(diag_tool, "out of memory"); diff --git a/driver/lib/target.c b/driver/lib/target.c @@ -406,6 +406,13 @@ int driver_target_from_triple(const char* triple, KitTargetSpec* out) { } } if (!os_set) { + /* No recognized OS token. This deliberately also covers bare-metal and + * vendor-only triples that carry no OS at all -- e.g. "x86_64", + * "aarch64-unknown", "riscv64-unknown-elf" -- which must keep resolving to + * a freestanding ELF/WASM target. We cannot tell an unrecognized OS token + * (e.g. "x86_64-pc-frobnicate") apart from those without a vendor/env token + * table, so erroring here would regress valid bare-metal triples. Left as a + * silent freestanding default by design; see findings note for B15. */ t.os = KIT_OS_FREESTANDING; t.obj = (t.arch == KIT_ARCH_WASM) ? KIT_OBJ_WASM : KIT_OBJ_ELF; } diff --git a/include/kit/cg.h b/include/kit/cg.h @@ -467,8 +467,9 @@ KIT_API KitStatus kit_cg_begin(KitCg*, KitObjBuilder* out, KIT_API KitStatus kit_cg_begin_unit(KitCg*, const KitCgUnitOptions*); KIT_API KitStatus kit_cg_end_unit(KitCg*); KIT_API KitStatus kit_cg_finish(KitCg*, const KitCgFinishOptions*); +/* Release the session, abandoning any in-progress object/unit state. Used on + * both the success-detach and error-abort paths. */ KIT_API KitStatus kit_cg_detach(KitCg*); -KIT_API KitStatus kit_cg_abort(KitCg*); KIT_API void kit_cg_free(KitCg*); /* Sticky source location. Function, scope, local, param, instruction, and diff --git a/include/kit/object.h b/include/kit/object.h @@ -355,7 +355,7 @@ KIT_API KitStatus kit_obj_section_format_flags(const KitObjFile*, KIT_API KitStatus kit_obj_symbol_by_name(const KitObjFile*, KitSlice name, KitObjSymInfo* out); -KIT_API KitStatus kit_obj_symiter_new(KitObjFile*, KitObjSymIter** out); +KIT_API KitStatus kit_obj_symiter_new(const KitObjFile*, KitObjSymIter** out); KIT_API KitIterResult kit_obj_symiter_next(KitObjSymIter*, KitObjSymInfo* out); KIT_API void kit_obj_symiter_free(KitObjSymIter*); @@ -401,7 +401,7 @@ KIT_API void kit_obj_rpathiter_free(KitObjRpathIter*); /* Dynamic symbol table (.dynsym / dyld export trie / PE export table). * Reuses the KitObjSymInfo shape and the KitObjSymIter handle — drive * it with kit_obj_symiter_next / _free. Empty on relocatable objects. */ -KIT_API KitStatus kit_obj_dynsymiter_new(KitObjFile*, KitObjSymIter** out); +KIT_API KitStatus kit_obj_dynsymiter_new(const KitObjFile*, KitObjSymIter** out); /* Dynamic relocations (.rela.dyn / .rela.plt / dyld binds / PE base relocs). * Reuses KitObjReloc and the KitObjRelocIter handle — drive it with diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -1956,8 +1956,8 @@ static int try_parse_builtin_call(Parser* p) { FrameSlotDesc fsd; memset(&fsd, 0, sizeof fsd); fsd.type = eptr_ty; - fsd.size = 8; - fsd.align = 8; + fsd.size = c_abi_sizeof(p->abi, eptr_ty); + fsd.align = c_abi_alignof(p->abi, eptr_ty); fsd.kind = FS_LOCAL; FrameSlot eslot = pcg_local(p, &fsd); pcg_push_local_typed(p, eslot, eptr_ty); @@ -1990,8 +1990,8 @@ static int try_parse_builtin_call(Parser* p) { FrameSlotDesc okd; memset(&okd, 0, sizeof okd); okd.type = ok_ty; - okd.size = 4; - okd.align = 4; + okd.size = c_abi_sizeof(p->abi, ok_ty); + okd.align = c_abi_alignof(p->abi, ok_ty); okd.kind = FS_LOCAL; FrameSlot okslot = pcg_local(p, &okd); pcg_push_local_typed(p, okslot, ok_ty); @@ -2192,9 +2192,15 @@ static void parse_primary(Parser* p) { perr(p, "expected expression"); } +/* Resolve `mname` inside `rec_ty`, recursing through anonymous struct/union + * members to arbitrary depth (like offsetof_find_member / find_field). On + * success accumulates the cumulative byte offset into *out_off, records the + * matched member type, and — for a bit-field leaf — the bit-field metadata + * pulled from the final ABIFieldLayout. */ static int find_record_member_path(Parser* p, const Type* rec_ty, Sym mname, - const Type** out_ty, u32 path[2], - u32* out_depth, const Field** out_field) { + const Type** out_ty, i64* out_off, + u16* out_bf_off, u16* out_bf_w, + u32* out_bf_ss) { const ABIRecordLayout* L; rec_ty = type_unqual(p->pool, rec_ty); if (!rec_ty || (rec_ty->kind != TY_STRUCT && rec_ty->kind != TY_UNION)) @@ -2204,68 +2210,35 @@ static int find_record_member_path(Parser* p, const Type* rec_ty, Sym mname, for (u16 i = 0; i < rec_ty->rec.nfields; ++i) { const Field* f = &rec_ty->rec.fields[i]; if (f->name == mname && mname != 0) { + const ABIFieldLayout* fl = &L->fields[i]; *out_ty = f->type; - path[0] = i; - *out_depth = 1; - if (out_field) *out_field = f; + *out_off += (i64)fl->offset; + if (f->flags & FIELD_BITFIELD) { + *out_bf_off = fl->bit_offset; + *out_bf_w = fl->bit_width; + *out_bf_ss = fl->storage_size; + } return 1; } - { - const Type* fty = type_unqual(p->pool, f->type); - if (!((f->flags & FIELD_ANON) && - (fty->kind == TY_STRUCT || fty->kind == TY_UNION))) { - continue; - } - const ABIRecordLayout* IL = c_abi_record_layout(p->abi, p->pool, fty); - if (!IL) continue; - for (u16 j = 0; j < fty->rec.nfields; ++j) { - const Field* ff = &fty->rec.fields[j]; - if (ff->name == mname && mname != 0) { - *out_ty = ff->type; - path[0] = i; - path[1] = j; - *out_depth = 2; - if (out_field) *out_field = ff; - return 1; - } - } + } + for (u16 i = 0; i < rec_ty->rec.nfields; ++i) { + const Field* f = &rec_ty->rec.fields[i]; + const Type* fty = type_unqual(p->pool, f->type); + i64 nested_off; + if (!((f->flags & FIELD_ANON) && + (fty->kind == TY_STRUCT || fty->kind == TY_UNION))) { + continue; + } + nested_off = *out_off + (i64)L->fields[i].offset; + if (find_record_member_path(p, fty, mname, out_ty, &nested_off, out_bf_off, + out_bf_w, out_bf_ss)) { + *out_off = nested_off; + return 1; } } return 0; } -static void cg_record_member_path(Parser* p, const Type* member_ty, - const u32* path, u32 depth, - const Field* field) { - /* Walk the path locally to compute the cumulative byte offset; pull - * bit-field metadata from the final ABIFieldLayout when applicable. The - * field/index/addr_offset CG ops are gone — pcg_lv_member folds the offset - * (and any bit-field meta) onto the TOS lvalue's aux for the next memop. */ - const Type* cur_ty = pcg_top_type(p); - i64 total_offset = 0; - u16 bf_off = 0; - u16 bf_w = 0; - u32 bf_ss = 0; - cur_ty = type_unqual(p->pool, cur_ty); - for (u32 i = 0; i < depth; ++i) { - const ABIRecordLayout* L = c_abi_record_layout(p->abi, p->pool, cur_ty); - const ABIFieldLayout* fl; - const Field* f; - if (!L) break; - fl = &L->fields[path[i]]; - f = &cur_ty->rec.fields[path[i]]; - total_offset += (i64)fl->offset; - if (i + 1u == depth && (f->flags & FIELD_BITFIELD)) { - bf_off = fl->bit_offset; - bf_w = fl->bit_width; - bf_ss = fl->storage_size; - } - cur_ty = type_unqual(p->pool, f->type); - } - (void)field; - pcg_lv_member(p, total_offset, member_ty, bf_off, bf_w, bf_ss); -} - static void parse_postfix(Parser* p) { VLABound* vla_bounds; p->last_pushed_vla_slot = FRAME_SLOT_NONE; @@ -2394,9 +2367,9 @@ static void parse_postfix(Parser* p) { const Type* lt = pcg_top_type(p); Sym mname; const Type* mty = NULL; - const Field* mf = NULL; - u32 path[2]; - u32 depth = 0; + i64 off = 0; + u16 bf_off = 0, bf_w = 0; + u32 bf_ss = 0; advance(p); /* '.' */ if (!lt || (lt->kind != TY_STRUCT && lt->kind != TY_UNION)) { perr(p, @@ -2408,9 +2381,10 @@ static void parse_postfix(Parser* p) { mname = p->cur.v.ident; advance(p); lt = type_unqual(p->pool, lt); - if (!find_record_member_path(p, lt, mname, &mty, path, &depth, &mf)) + if (!find_record_member_path(p, lt, mname, &mty, &off, &bf_off, &bf_w, + &bf_ss)) perr(p, "no such member"); - cg_record_member_path(p, mty, path, depth, mf); + pcg_lv_member(p, off, mty, bf_off, bf_w, bf_ss); continue; } if (is_punct(&t, P_ARROW)) { @@ -2418,9 +2392,9 @@ static void parse_postfix(Parser* p) { const Type* rec_ty; Sym mname; const Type* mty = NULL; - const Field* mf = NULL; - u32 path[2]; - u32 depth = 0; + i64 off = 0; + u16 bf_off = 0, bf_w = 0; + u32 bf_ss = 0; advance(p); /* `->` */ to_rvalue(p); lt0 = pcg_top_type(p); @@ -2436,10 +2410,11 @@ static void parse_postfix(Parser* p) { } mname = p->cur.v.ident; advance(p); - if (!find_record_member_path(p, rec_ty, mname, &mty, path, &depth, &mf)) + if (!find_record_member_path(p, rec_ty, mname, &mty, &off, &bf_off, &bf_w, + &bf_ss)) perr(p, "no such member"); pcg_deref(p, rec_ty); - cg_record_member_path(p, mty, path, depth, mf); + pcg_lv_member(p, off, mty, bf_off, bf_w, bf_ss); continue; } break; @@ -2470,12 +2445,7 @@ void parse_unary(Parser* p) { fsd.kind = FS_LOCAL; fsd.flags = FSF_NONE; slot = pcg_local(p, &fsd); - if (lit_ty && (lit_ty->kind == TY_ARRAY || lit_ty->kind == TY_STRUCT || - lit_ty->kind == TY_UNION)) { - init_at(p, slot, lit_ty, 0, lit_ty); - } else { - init_at(p, slot, lit_ty, 0, lit_ty); - } + init_at(p, slot, lit_ty, 0, lit_ty); pcg_push_local_typed(p, slot, lit_ty); return; } @@ -3326,8 +3296,6 @@ static void parse_lor(Parser* p) { } } -static const Type* common_fp_type(Parser* p, const Type* a, const Type* b); - static void parse_ternary(Parser* p) { parse_lor(p); if (!is_punct(&p->cur, '?')) return; diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c @@ -691,6 +691,10 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) { advance(p); seen = 1; } else if (is_kw(p, &t, KW_FLOAT) || is_kw(p, &t, KW_FLOAT16)) { + /* _Float16 is intentionally aliased to 32-bit float (a deliberate + * approximation; see test/parse/cases/float16_01_decl, which asserts + * sizeof(_Float16) == sizeof(float)). Real 16-bit semantics would be a + * separate feature. */ acc.saw_float = 1; acc.saw_explicit_type = 1; advance(p); diff --git a/lang/c/type/type.c b/lang/c/type/type.c @@ -207,13 +207,6 @@ TagId type_tag_new(Pool* p, TagDeclKind kind, Sym spelling, SrcLoc loc) { return (TagId)(c->next_tag++); } -const TagDecl* type_tag_get(Pool* p, TagId id) { - (void)p; - (void)id; - /* TagDecl table is parser-territory; not modeled in v1. */ - return NULL; -} - TypeRecordBuilder* type_record_begin(Pool* p, TypeKind kind, TagId tag_id, Sym tag) { TypeRecordOpts opts; diff --git a/lang/c/type/type.h b/lang/c/type/type.h @@ -135,7 +135,6 @@ const Type* type_qualified(Pool*, const Type*, u16 qual); * size/alignment, and bitfield storage are target ABI facts. */ typedef struct TypeRecordBuilder TypeRecordBuilder; TagId type_tag_new(Pool*, TagDeclKind, Sym spelling, SrcLoc); -const TagDecl* type_tag_get(Pool*, TagId); TypeRecordBuilder* type_record_begin(Pool*, TypeKind kind, TagId, Sym tag); /* TY_STRUCT or TY_UNION */ diff --git a/lang/cpp/pp/pp_directive.c b/lang/cpp/pp/pp_directive.c @@ -5,6 +5,7 @@ static void destringize(Pp* pp, const Tok* str_tok, char* out, size_t cap, size_t* out_len); +static void pp_warn(Pp* pp, SrcLoc loc, const char* fmt, ...); /* ============================================================ * If-stack @@ -959,7 +960,7 @@ static int pragma_num_u32(Pp* pp, const Tok* t, u32* out) { return 1; } -static void handle_pragma_pack(Pp* pp, const Tok* line, u32 n) { +static void handle_pragma_pack(Pp* pp, const Tok* line, u32 n, SrcLoc loc) { u32 i = 0; if (n < 3 || line[0].kind != TOK_IDENT) return; { @@ -982,6 +983,10 @@ static void handle_pragma_pack(Pp* pp, const Tok* line, u32 n) { if (pp->pack_stack_n < (u32)(sizeof pp->pack_stack / sizeof pp->pack_stack[0])) { pp->pack_stack[pp->pack_stack_n++] = pp->pack_align; + } else { + pp_warn(pp, loc, + "#pragma pack(push): pack stack overflow (max %u); push dropped", + (u32)(sizeof pp->pack_stack / sizeof pp->pack_stack[0])); } ++i; if (i < n && line[i].kind == TOK_PUNCT && line[i].v.punct == ',') { @@ -1005,7 +1010,7 @@ static void handle_pragma_pack(Pp* pp, const Tok* line, u32 n) { static void do_pragma(Pp* pp, const Tok* line, u32 n, SrcLoc loc) { /* Forward unrecognised pragmas to the output. STDC pragmas pass * through too; we don't act on them yet. */ - handle_pragma_pack(pp, line, n); + handle_pragma_pack(pp, line, n, loc); emit_pragma_line(pp, line, n, loc); } diff --git a/lang/cpp/pp/pp_expand.c b/lang/cpp/pp/pp_expand.c @@ -86,33 +86,6 @@ HidesetId hs_add(Pp* pp, HidesetId id, Sym s) { return hs_register(pp, buf, n + 1); } -/* Used by token-paste in stage 5; declared early so the rest of the file - * doesn't grow forward decls. */ -__attribute__((unused)) static HidesetId hs_intersect(Pp* pp, HidesetId a, - HidesetId b) { - Sym buf[64]; - Hideset *ha, *hb; - u32 i, j, k; - if (a == HS_EMPTY || b == HS_EMPTY) return HS_EMPTY; - if (a == b) return a; - ha = pp->hsets[a]; - hb = pp->hsets[b]; - /* Both sorted; standard merge intersection. */ - i = j = k = 0; - while (i < ha->n && j < hb->n) { - if (ha->names[i] == hb->names[j]) { - buf[k++] = ha->names[i]; - ++i; - ++j; - } else if (ha->names[i] < hb->names[j]) { - ++i; - } else { - ++j; - } - } - return hs_register(pp, buf, k); -} - /* ============================================================ * Macro table * ============================================================ */ diff --git a/mk/driver_srcs.mk b/mk/driver_srcs.mk @@ -68,6 +68,7 @@ DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib 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,STRIP OBJCOPY,driver/lib/objedit.c) DRIVER_SRCS += $(call need-any,RUN,driver/lib/wasm_run.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) diff --git a/src/abi/abi.c b/src/abi/abi.c @@ -87,10 +87,6 @@ ABITypeInfo abi_cg_type_info(TargetABI* a, KitCgTypeId id) { } } -ABITypeInfo abi_internal_type_info(TargetABI* a, KitCgTypeId id) { - return abi_cg_type_info(a, id); -} - u32 abi_cg_sizeof(TargetABI* a, KitCgTypeId id) { return abi_cg_type_info(a, id).size; } @@ -153,6 +149,39 @@ const ABIRecordLayout* abi_cg_record_layout(TargetABI* a, KitCgTypeId id) { return L; } +/* ---- shared classifier primitives ---- + * + * The per-ABI classifiers (abi_sysv_x64.c, abi_win64_x64.c, abi_aapcs64.c, + * abi_rv64.c) share these byte-identical building blocks; the ABI-specific + * scalar/aggregate rules stay in their own TUs. */ + +/* A void / zero-size argument is ignored (no parts, no register/stack slot). */ +void abi_classify_void(ABIArgInfo* out) { + memset(out, 0, sizeof *out); + out->kind = ABI_ARG_IGNORE; +} + +/* A 16-byte integer scalar (__int128 / __uint128) passed/returned as two + * INTEGER eightbytes: low half in the lower-numbered register, high in the + * next. Used by the SysV-x64, Win64 (mingw), and AAPCS64 classifiers, which + * all agree on this shape. */ +void abi_classify_int128_pair(TargetABI* a, ABIArgInfo* out) { + ABIArgPart* parts = arena_array(a->c->tu, ABIArgPart, 2); + memset(parts, 0, sizeof(ABIArgPart) * 2); + for (u32 i = 0; i < 2; ++i) { + parts[i].cls = ABI_CLASS_INT; + parts[i].loc = ABI_LOC_REG; + parts[i].size = 8; + parts[i].align = 8; + parts[i].src_offset = i * 8; + } + out->kind = ABI_ARG_DIRECT; + out->flags = ABI_AF_NONE; + out->parts = parts; + out->nparts = 2; + out->indirect_align = 0; +} + /* ---- function classification (vtabled) ---- */ const ABIFuncInfo* abi_cg_func_info(TargetABI* a, KitCgTypeId fn_type) { @@ -178,12 +207,13 @@ u32 abi_stack_probe_interval(TargetABI* a) { ABITypeInfo abi_va_list_info(TargetABI* a) { return a->vt->va_list_info; } ABIVaListInfo abi_va_list_layout(TargetABI* a) { + /* va_list_info is the single source of truth for the va_list ABITypeInfo; + * the layout's .type is always derived from it so the two cannot drift. */ ABIVaListInfo out = a->vt->va_list_layout; - if (out.kind == ABI_VA_LIST_OPAQUE) { - out.type = a->vt->va_list_info; - if (out.type.scalar_kind == ABI_SC_PTR && out.type.size == 8u) - out.kind = ABI_VA_LIST_POINTER; - } + out.type = a->vt->va_list_info; + if (out.kind == ABI_VA_LIST_OPAQUE && out.type.scalar_kind == ABI_SC_PTR && + out.type.size == 8u) + out.kind = ABI_VA_LIST_POINTER; return out; } diff --git a/src/abi/abi_aapcs64.c b/src/abi/abi_aapcs64.c @@ -19,22 +19,9 @@ #include "core/core.h" static void classify_scalar(TargetABI* a, KitCgTypeId t, ABIArgInfo* out) { - ABITypeInfo ti = abi_internal_type_info(a, t); + ABITypeInfo ti = abi_cg_type_info(a, t); if (ti.scalar_kind == ABI_SC_INT && ti.size == 16) { - ABIArgPart* parts = arena_array(a->c->tu, ABIArgPart, 2); - memset(parts, 0, sizeof(ABIArgPart) * 2); - for (u32 i = 0; i < 2; ++i) { - parts[i].cls = ABI_CLASS_INT; - parts[i].loc = ABI_LOC_REG; - parts[i].size = 8; - parts[i].align = 8; - parts[i].src_offset = i * 8; - } - out->kind = ABI_ARG_DIRECT; - out->flags = ABI_AF_NONE; - out->parts = parts; - out->nparts = 2; - out->indirect_align = 0; + abi_classify_int128_pair(a, out); return; } out->kind = ABI_ARG_DIRECT; @@ -53,16 +40,11 @@ static void classify_scalar(TargetABI* a, KitCgTypeId t, ABIArgInfo* out) { out->nparts = 1; } -static void classify_void(ABIArgInfo* out) { - memset(out, 0, sizeof *out); - out->kind = ABI_ARG_IGNORE; -} - static void classify_aggregate(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, int is_return) { - ABITypeInfo ti = abi_internal_type_info(a, t); + ABITypeInfo ti = abi_cg_type_info(a, t); if (ti.size == 0) { - classify_void(out); + abi_classify_void(out); return; } /* AAPCS64: aggregates ≤ 16 bytes pass in up to 2 GPRs (or HFA in FP regs; @@ -100,7 +82,7 @@ static void classify_one(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, int is_return) { const CgType* ty = cg_type_get(a->c, t); if (!ty || ty->kind == KIT_CG_TYPE_VOID) { - classify_void(out); + abi_classify_void(out); return; } switch (ty->kind) { @@ -148,8 +130,7 @@ ABIFuncInfo* aapcs64_compute_func_info(TargetABI* a, KitCgTypeId fn) { const ABIVtable aapcs64_vtable = { .compute_func_info = aapcs64_compute_func_info, .va_list_info = {32, 8, ABI_SC_VOID, 0, 0, 0}, - .va_list_layout = {.type = {32, 8, ABI_SC_VOID, 0, 0, 0}, - .kind = ABI_VA_LIST_AAPCS64, + .va_list_layout = {.kind = ABI_VA_LIST_AAPCS64, .stack_offset = 0, .gr_top_offset = 8, .vr_top_offset = 16, diff --git a/src/abi/abi_aapcs64_windows.c b/src/abi/abi_aapcs64_windows.c @@ -71,8 +71,7 @@ const ABIVtable aapcs64_windows_vtable = { * in src/arch/aa64/native.c. */ .stack_probe_interval = 4096, .va_list_info = {8, 8, ABI_SC_PTR, 0, 0, 0}, - .va_list_layout = {.type = {8, 8, ABI_SC_PTR, 0, 0, 0}, - .kind = ABI_VA_LIST_POINTER, + .va_list_layout = {.kind = ABI_VA_LIST_POINTER, .gp_reg_count = 8, .fp_reg_count = 0, .gp_slot_size = 8, diff --git a/src/abi/abi_apple_arm64.c b/src/abi/abi_apple_arm64.c @@ -43,6 +43,5 @@ static ABIFuncInfo* apple_arm64_compute_func_info(TargetABI* a, const ABIVtable apple_arm64_vtable = { .compute_func_info = apple_arm64_compute_func_info, .va_list_info = {8, 8, ABI_SC_PTR, 0, 0, 0}, - .va_list_layout = {.type = {8, 8, ABI_SC_PTR, 0, 0, 0}, - .kind = ABI_VA_LIST_POINTER}, + .va_list_layout = {.kind = ABI_VA_LIST_POINTER}, }; diff --git a/src/abi/abi_apple_x64.c b/src/abi/abi_apple_x64.c @@ -17,8 +17,7 @@ static ABIFuncInfo* apple_x64_compute_func_info(TargetABI* a, KitCgTypeId fn) { const ABIVtable apple_x64_vtable = { .compute_func_info = apple_x64_compute_func_info, .va_list_info = {24, 8, ABI_SC_VOID, 0, 0, 0}, - .va_list_layout = {.type = {24, 8, ABI_SC_VOID, 0, 0, 0}, - .kind = ABI_VA_LIST_SYSV_X64, + .va_list_layout = {.kind = ABI_VA_LIST_SYSV_X64, .stack_offset = 8, .gr_top_offset = 16, .gr_offs_offset = 0, diff --git a/src/abi/abi_internal.h b/src/abi/abi_internal.h @@ -17,6 +17,9 @@ typedef struct ABIVtable { * lowered by generic CG as multiple addressable machine-word lanes, or 0 * when the target ABI treats it as one scalar value. */ u32 (*scalar_split_lane_size)(TargetABI*, KitCgTypeId); + /* The single source of truth for the va_list ABITypeInfo. abi_va_list_layout + * always derives ABIVaListInfo.type from this, so vtables leave + * va_list_layout.type zero-initialized rather than restating it. */ ABITypeInfo va_list_info; ABIVaListInfo va_list_layout; /* Stack-probe granularity. 0 = the target OS auto-grows the stack on any @@ -68,7 +71,10 @@ struct TargetABI { RecordLayoutCacheEntry* rec_cache; }; -/* Shared helpers exposed to per-ABI TUs. */ -ABITypeInfo abi_internal_type_info(TargetABI*, KitCgTypeId); +/* Shared classifier primitives implemented in abi.c. The byte-identical + * building blocks the per-ABI classifiers (sysv_x64 / win64_x64 / aapcs64 / + * rv64) reuse; ABI-specific scalar/aggregate rules stay in those TUs. */ +void abi_classify_void(ABIArgInfo* out); +void abi_classify_int128_pair(TargetABI* a, ABIArgInfo* out); #endif diff --git a/src/abi/abi_rv64.c b/src/abi/abi_rv64.c @@ -112,7 +112,7 @@ static u32 riscv_collect_leaves(TargetABI* a, KitCgTypeId tid, u32 base_off, } if (t->kind == KIT_CG_TYPE_ARRAY) { if (t->array.count == 0) return written; /* zero-length array: skip */ - ABITypeInfo elem = abi_internal_type_info(a, t->array.elem); + ABITypeInfo elem = abi_cg_type_info(a, t->array.elem); if (elem.size == 0) return written; for (u64 i = 0; i < t->array.count; ++i) { u32 off = base_off + (u32)(i * elem.size); @@ -122,7 +122,7 @@ static u32 riscv_collect_leaves(TargetABI* a, KitCgTypeId tid, u32 base_off, return written; } /* Scalar leaf (including pointer). */ - ABITypeInfo ti = abi_internal_type_info(a, tid); + ABITypeInfo ti = abi_cg_type_info(a, tid); if (ti.size == 0) return written; if (written >= cap) return written + 1u; out[written].offset = base_off; @@ -133,7 +133,7 @@ static u32 riscv_collect_leaves(TargetABI* a, KitCgTypeId tid, u32 base_off, static void classify_scalar(TargetABI* a, KitCgTypeId t, ABIArgInfo* out) { RiscvAbiDesc d = riscv_abi_desc(a); - ABITypeInfo ti = abi_internal_type_info(a, t); + ABITypeInfo ti = abi_cg_type_info(a, t); /* A scalar twice the GPR width that lives in the integer/long-double space * (or a soft-float double) is carried as an aligned pair of GPRs. On rv64 * this is the 16-byte long double / __int128 pair; on rv32 it is the 8-byte @@ -180,7 +180,7 @@ static void classify_scalar(TargetABI* a, KitCgTypeId t, ABIArgInfo* out) { static u32 riscv32_scalar_split_lane_size(TargetABI* a, KitCgTypeId t) { RiscvAbiDesc d = riscv_abi_desc(a); - ABITypeInfo ti = abi_internal_type_info(a, t); + ABITypeInfo ti = abi_cg_type_info(a, t); int fp_part; if (d.gpr_bytes != 4u) return 0; fp_part = (ti.scalar_kind == ABI_SC_FLOAT) && @@ -191,11 +191,6 @@ static u32 riscv32_scalar_split_lane_size(TargetABI* a, KitCgTypeId t) { return 0; } -static void classify_void(ABIArgInfo* out) { - memset(out, 0, sizeof *out); - out->kind = ABI_ARG_IGNORE; -} - /* Try the psABI floating-point aggregate refinements. Returns 1 if `out` * was populated, 0 to fall back to the generic GPR-pair packing. */ static int riscv_classify_fp_aggregate(TargetABI* a, KitCgTypeId t, @@ -241,9 +236,9 @@ static int riscv_classify_fp_aggregate(TargetABI* a, KitCgTypeId t, static void classify_aggregate(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, int is_return) { RiscvAbiDesc d = riscv_abi_desc(a); - ABITypeInfo ti = abi_internal_type_info(a, t); + ABITypeInfo ti = abi_cg_type_info(a, t); if (ti.size == 0) { - classify_void(out); + abi_classify_void(out); return; } if (ti.size <= d.aggregate_gpr_bytes) { @@ -282,7 +277,7 @@ static void classify_one(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, int is_return) { const CgType* ty = cg_type_get(a->c, t); if (!ty || ty->kind == KIT_CG_TYPE_VOID) { - classify_void(out); + abi_classify_void(out); return; } switch (ty->kind) { @@ -332,8 +327,7 @@ const ABIVtable rv64_vtable = { * varargs are passed in GPRs, so there is no separate FP save area. The * gp_reg_count/gp_slot_size fields let native_frame_va_save_bytes size that * area from the ABI rather than a backend constant. */ - .va_list_layout = {.type = {8, 8, ABI_SC_PTR, 0, 0, 0}, - .kind = ABI_VA_LIST_POINTER, + .va_list_layout = {.kind = ABI_VA_LIST_POINTER, .gp_reg_count = 8, .fp_reg_count = 0, .gp_slot_size = 8, @@ -347,8 +341,7 @@ const ABIVtable rv32_vtable = { /* ILP32* va_list is a plain 4-byte pointer; the variadic register-save * area is the 8 integer arg registers (a0..a7) spilled contiguously = * 32 bytes. FP varargs are passed in GPRs, so there is no FP save area. */ - .va_list_layout = {.type = {4, 4, ABI_SC_PTR, 0, 0, 0}, - .kind = ABI_VA_LIST_POINTER, + .va_list_layout = {.kind = ABI_VA_LIST_POINTER, .gp_reg_count = 8, .fp_reg_count = 0, .gp_slot_size = 4, diff --git a/src/abi/abi_sysv_x64.c b/src/abi/abi_sysv_x64.c @@ -11,31 +11,13 @@ #include "core/arena.h" #include "core/core.h" -static void classify_void(ABIArgInfo* out) { - memset(out, 0, sizeof *out); - out->kind = ABI_ARG_IGNORE; -} - static void classify_scalar(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, int is_return) { - ABITypeInfo ti = abi_internal_type_info(a, t); + ABITypeInfo ti = abi_cg_type_info(a, t); /* __int128 / __uint128: SysV psABI classifies as two INTEGER eightbytes * (rdi+rsi etc. for args; rax+rdx for return). */ if (ti.scalar_kind == ABI_SC_INT && ti.size == 16) { - ABIArgPart* parts = arena_array(a->c->tu, ABIArgPart, 2); - memset(parts, 0, sizeof(ABIArgPart) * 2); - for (u32 i = 0; i < 2; ++i) { - parts[i].cls = ABI_CLASS_INT; - parts[i].loc = ABI_LOC_REG; - parts[i].size = 8; - parts[i].align = 8; - parts[i].src_offset = i * 8; - } - out->kind = ABI_ARG_DIRECT; - out->flags = ABI_AF_NONE; - out->parts = parts; - out->nparts = 2; - out->indirect_align = 0; + abi_classify_int128_pair(a, out); return; } /* long double: 80-bit x87 (padded to 16B with 16B alignment). SysV class @@ -107,7 +89,7 @@ static int classify_range(TargetABI* a, KitCgTypeId t, u32 base, if (ty->kind == KIT_CG_TYPE_ENUM) { return classify_range(a, ty->enum_.base, base, cls); } - ti = abi_internal_type_info(a, t); + ti = abi_cg_type_info(a, t); switch (ty->kind) { case KIT_CG_TYPE_BOOL: case KIT_CG_TYPE_INT: @@ -118,7 +100,7 @@ static int classify_range(TargetABI* a, KitCgTypeId t, u32 base, return mark_eightbytes(cls, base, ti.size, SYSV_SSE); return 0; case KIT_CG_TYPE_ARRAY: { - ABITypeInfo ei = abi_internal_type_info(a, ty->array.elem); + ABITypeInfo ei = abi_cg_type_info(a, ty->array.elem); for (u64 i = 0; i < ty->array.count; ++i) { if (i > UINT32_MAX || ei.size > UINT32_MAX || base > UINT32_MAX - (u32)(i * ei.size)) @@ -134,7 +116,7 @@ static int classify_range(TargetABI* a, KitCgTypeId t, u32 base, for (u32 i = 0; i < ty->record.nfields; ++i) { const CgTypeField* f = &ty->record.fields[i]; const ABIFieldLayout* fl = &L->fields[i]; - ABITypeInfo fi = abi_internal_type_info(a, f->type); + ABITypeInfo fi = abi_cg_type_info(a, f->type); if ((f->flags & KIT_CG_FIELD_BITFIELD) != 0) { if (fl->bit_width == 0) continue; if (!mark_eightbytes(cls, base + fl->offset, fl->storage_size, @@ -157,9 +139,9 @@ static int classify_range(TargetABI* a, KitCgTypeId t, u32 base, static void classify_aggregate(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, int is_return) { - ABITypeInfo ti = abi_internal_type_info(a, t); + ABITypeInfo ti = abi_cg_type_info(a, t); if (ti.size == 0) { - classify_void(out); + abi_classify_void(out); return; } if (ti.size <= 16) { @@ -203,7 +185,7 @@ static void classify_one(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, int is_return) { const CgType* ty = cg_type_get(a->c, t); if (!ty || ty->kind == KIT_CG_TYPE_VOID) { - classify_void(out); + abi_classify_void(out); return; } switch (ty->kind) { @@ -299,8 +281,7 @@ static ABIFuncInfo* sysv_x64_compute_func_info(TargetABI* a, KitCgTypeId fn) { const ABIVtable sysv_x64_vtable = { .compute_func_info = sysv_x64_compute_func_info, .va_list_info = {24, 8, ABI_SC_VOID, 0, 0, 0}, - .va_list_layout = {.type = {24, 8, ABI_SC_VOID, 0, 0, 0}, - .kind = ABI_VA_LIST_SYSV_X64, + .va_list_layout = {.kind = ABI_VA_LIST_SYSV_X64, .stack_offset = 8, .gr_top_offset = 16, .gr_offs_offset = 0, diff --git a/src/abi/abi_win64_x64.c b/src/abi/abi_win64_x64.c @@ -25,14 +25,9 @@ #include "core/arena.h" #include "core/core.h" -static void classify_void(ABIArgInfo* out) { - memset(out, 0, sizeof *out); - out->kind = ABI_ARG_IGNORE; -} - static void classify_scalar(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, int is_return) { - ABITypeInfo ti = abi_internal_type_info(a, t); + ABITypeInfo ti = abi_cg_type_info(a, t); (void)is_return; /* __int128 / __uint128: mingw/GCC convention emits two INTEGER * eightbytes (rcx+rdx for args, rax+rdx for return) -- same shape @@ -40,20 +35,7 @@ static void classify_scalar(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, * aggregates, but mingw is kit's interop target on Windows and * mingw matches SysV here. */ if (ti.scalar_kind == ABI_SC_INT && ti.size == 16) { - ABIArgPart* parts = arena_array(a->c->tu, ABIArgPart, 2); - memset(parts, 0, sizeof(ABIArgPart) * 2); - for (u32 i = 0; i < 2; ++i) { - parts[i].cls = ABI_CLASS_INT; - parts[i].loc = ABI_LOC_REG; - parts[i].size = 8; - parts[i].align = 8; - parts[i].src_offset = i * 8; - } - out->kind = ABI_ARG_DIRECT; - out->flags = ABI_AF_NONE; - out->parts = parts; - out->nparts = 2; - out->indirect_align = 0; + abi_classify_int128_pair(a, out); return; } /* long double on Win64 is 64-bit double (both MSVC and mingw, unless @@ -97,9 +79,9 @@ static void classify_scalar(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, static void classify_aggregate(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, int is_return) { - ABITypeInfo ti = abi_internal_type_info(a, t); + ABITypeInfo ti = abi_cg_type_info(a, t); if (ti.size == 0) { - classify_void(out); + abi_classify_void(out); return; } /* Win64: aggregates pass by value only when the size is exactly one @@ -132,7 +114,7 @@ static void classify_one(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, int is_return) { const CgType* ty = cg_type_get(a->c, t); if (!ty || ty->kind == KIT_CG_TYPE_VOID) { - classify_void(out); + abi_classify_void(out); return; } switch (ty->kind) { @@ -180,6 +162,5 @@ const ABIVtable win64_x64_vtable = { * __chkstk call on this (frame_size > one page). See x64_build_prologue. */ .stack_probe_interval = 4096, .va_list_info = {8, 8, ABI_SC_PTR, 0, 0, 0}, - .va_list_layout = {.type = {8, 8, ABI_SC_PTR, 0, 0, 0}, - .kind = ABI_VA_LIST_POINTER}, + .va_list_layout = {.kind = ABI_VA_LIST_POINTER}, }; diff --git a/src/api/compile.c b/src/api/compile.c @@ -495,7 +495,7 @@ KitStatus kit_compile_session_compile_cg(KitCompileSession* s, unit_open = 0; kit_frontend_commit(s->frontend); } else if (unit_open) { - (void)kit_cg_abort(cg); + (void)kit_cg_detach(cg); } return st; } @@ -571,21 +571,38 @@ static KitStatus asm_frontend_compile(KitFrontendState* frontend, Compiler* c; AsmLexer* lex; MCEmitter* mc; + KitDiagSink* diag; + u32 errors0; (void)opts; if (!fe || !fe->c || !input || !out) return KIT_INVALID; c = fe->c; + diag = c->ctx ? c->ctx->diag : NULL; + errors0 = diag ? diag->errors : 0; metrics_scope_begin(c, "compile.asm.lex_open"); lex = asm_lex_open_mem(c, input->name.s, input->bytes.s, input->bytes.len); metrics_scope_end(c, "compile.asm.lex_open"); metrics_scope_begin(c, "compile.asm.mc_new"); mc = mc_new(c, (ObjBuilder*)out); metrics_scope_end(c, "compile.asm.mc_new"); + if (!lex || !mc) { + /* Allocation failed before we could parse anything. Release whichever + * half we did get and report out-of-memory rather than a silent KIT_OK. */ + mc_free(mc); + asm_lex_close(lex); + return KIT_NOMEM; + } metrics_scope_begin(c, "compile.asm.parse"); asm_parse(c, lex, mc); metrics_scope_end(c, "compile.asm.parse"); metrics_scope_begin(c, "compile.asm.mc_free"); mc_free(mc); + asm_lex_close(lex); metrics_scope_end(c, "compile.asm.mc_free"); + /* asm_parse reports hard errors via panic (longjmp to the compile-obj + * setjmp frame, which yields KIT_ERR), so reaching here means the parse + * completed; but propagate any soft diagnostics the sink recorded instead + * of unconditionally claiming success. */ + if (diag && diag->errors > errors0) return KIT_ERR; return KIT_OK; } diff --git a/src/api/config_stubs.c b/src/api/config_stubs.c @@ -287,20 +287,6 @@ void debug_emit_row(Debug* d, ObjSecId text_section_id, u32 text_offset, (void)loc; } -u32 debug_loclist_new(Debug* d) { - (void)d; - return 0; -} - -void debug_loclist_add(Debug* d, u32 id, u32 begin_pc, u32 end_pc, - DebugVarLoc loc) { - (void)d; - (void)id; - (void)begin_pc; - (void)end_pc; - (void)loc; -} - void debug_emit(Debug* d) { (void)d; } KitStatus kit_dwarf_open(const KitContext* ctx, const KitObjFile* obj, diff --git a/src/api/link.c b/src/api/link.c @@ -22,6 +22,7 @@ #include "cg/internal.h" #include "cg/ir_recorder.h" #include "core/core.h" +#include "core/diag.h" #include "link/link_internal.h" KitJit* kit_jit_from_image(LinkImage*); @@ -66,6 +67,19 @@ static KitStatus link_session_remember_publish_obj(KitLinkSession* s, return KIT_OK; } +/* These KitLinkSessionOptions fields have no plumbing into the Linker yet: + * the shared-library DT_* knobs (soname/rpaths/runpaths/exports/ + * allow_undefined) and the build-id selector (build_id_mode/bytes/len; the + * ELF writer currently emits a fixed image-hash note regardless of mode). + * Until they are wired through, warn rather than silently honor the default + * instead of the caller's request. */ +static void link_warn_ignored_opt(Compiler* c, const char* name) { + DiagSink* diag = (c && c->ctx) ? c->ctx->diag : NULL; + if (!diag) return; + diag_emit(diag, DIAG_WARN, SRCLOC_NONE, + "link: option '%s' is not yet supported and is ignored", name); +} + KitStatus kit_link_session_new(KitCompiler* c, const KitLinkSessionOptions* opts, KitLinkSession** out) { @@ -113,14 +127,15 @@ KitStatus kit_link_session_new(KitCompiler* c, link_set_gc_sections(l, opts->gc_sections); link_set_strip_debug(l, opts->strip_debug); link_set_pie(l, 1); - (void)opts->soname; - (void)opts->rpaths; - (void)opts->nrpaths; - (void)opts->runpaths; - (void)opts->nrunpaths; - (void)opts->exports; - (void)opts->nexports; - (void)opts->allow_undefined; + if (opts->soname.s && opts->soname.len) + link_warn_ignored_opt(s->c, "-soname"); + if (opts->rpaths && opts->nrpaths) link_warn_ignored_opt(s->c, "-rpath"); + if (opts->runpaths && opts->nrunpaths) + link_warn_ignored_opt(s->c, "-rpath (DT_RUNPATH)"); + if (opts->exports && opts->nexports) + link_warn_ignored_opt(s->c, "--export-symbol"); + if (opts->allow_undefined) + link_warn_ignored_opt(s->c, "--allow-shlib-undefined"); break; case KIT_LINK_OUTPUT_RELOCATABLE: break; @@ -149,6 +164,13 @@ KitStatus kit_link_session_new(KitCompiler* c, opts->linker_script->entry.len)) { link_set_entry(l, KIT_SLICE_LIT("WinMainCRTStartup")); } + /* TODO(build-id): the ELF writer (src/obj/elf/link.c) emits a fixed + * image-hash build-id note unconditionally for non-scripted layouts; the + * caller's mode selection is not threaded through, so a note is emitted + * even for KIT_BUILDID_NONE and uuid/user-bytes modes get the default hash. + * Honoring the mode means carrying it into LinkImage + the ELF/Mach-O + * writers (and is a no-op for the common default), so it is left as a known + * gap rather than warned about on the default path. */ (void)opts->build_id_mode; (void)opts->build_id_bytes; (void)opts->build_id_len; diff --git a/src/api/object_file.c b/src/api/object_file.c @@ -267,7 +267,7 @@ KitStatus kit_obj_symbol_by_name(const KitObjFile* f, KitSlice name, } struct KitObjSymIter { - KitObjFile* file; + const KitObjFile* file; ObjSymIter* inner; /* .symtab walk; NULL when iterating the dynamic table */ u32 dyn_idx; /* next index into obj_image dynsyms (dynamic mode) */ int dynamic; @@ -276,7 +276,8 @@ struct KitObjSymIter { /* Shared by kit_obj_symiter_new (.symtab) and kit_obj_dynsymiter_new * (.dynsym). When dynamic, the inner ObjSymIter is unused and we walk the * image's dynamic symbol table by index. */ -static KitStatus symiter_make(KitObjFile* f, int dynamic, KitObjSymIter** out) { +static KitStatus symiter_make(const KitObjFile* f, int dynamic, + KitObjSymIter** out) { Heap* h; KitObjSymIter* it; if (!f || !out) return KIT_INVALID; @@ -298,11 +299,11 @@ static KitStatus symiter_make(KitObjFile* f, int dynamic, KitObjSymIter** out) { return KIT_OK; } -KitStatus kit_obj_symiter_new(KitObjFile* f, KitObjSymIter** out) { +KitStatus kit_obj_symiter_new(const KitObjFile* f, KitObjSymIter** out) { return symiter_make(f, 0, out); } -KitStatus kit_obj_dynsymiter_new(KitObjFile* f, KitObjSymIter** out) { +KitStatus kit_obj_dynsymiter_new(const KitObjFile* f, KitObjSymIter** out) { return symiter_make(f, 1, out); } diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c @@ -129,8 +129,6 @@ enum { * physically the same address, expressed via aa_fp_off_tail_out_arg. * ========================================================================== */ -static u32 align_up_u32(u32 v, u32 align); - typedef struct AAFrameLayout { u32 slot_bytes; /* sum of aa_frame_slot reservations (callee-saves + locals * + spills + sret/variadic) */ @@ -306,7 +304,7 @@ static inline i32 aa_fp_off_home_slot(u32 i) { return (i32)(AA_FRAME_SAVE_SIZE + i * 8u); } -static void aa_panic(AANativeTarget* a, const char* msg) { +static _Noreturn void aa_panic(AANativeTarget* a, const char* msg) { compiler_panic(a->base.c, a->loc, "aarch64 native target: %s", msg); } @@ -332,11 +330,6 @@ static void aa_patch32(ObjBuilder* obj, ObjSecId sec, u32 off, u32 word) { obj_patch(obj, sec, off, b, sizeof b); } -static u32 align_up_u32(u32 v, u32 align) { - u32 mask = align ? align - 1u : 0u; - return (v + mask) & ~mask; -} - static u32 type_size32(NativeTarget* t, KitCgTypeId type) { u64 n = type ? cg_type_size(t->c, type) : 8u; if (n == 0) n = 8u; @@ -507,24 +500,24 @@ static __attribute__((unused)) u32 aa_mrs_tpidr_el0(u32 rt) { return 0xd53bd040u | (rt & 0x1fu); } +/* The scalar-FP / bit-packing encoders below delegate to the single-source + * isa.h encoders so encode/decode stay in lockstep. ftype 0=single, 1=double; + * the historical aa_* signatures (and call sites) are preserved as thin + * wrappers. */ static u32 aa_fp_bin(u32 op, u32 is_double, u32 rd, u32 rn, u32 rm) { - return (is_double ? 0x1e600000u : 0x1e200000u) | op | ((rm & 0x1fu) << 16) | - ((rn & 0x1fu) << 5) | (rd & 0x1fu); + return aa64_fp_dp2(is_double, op, rd, rn, rm); } static u32 aa_fcmp(u32 is_double, u32 rn, u32 rm) { - return (is_double ? 0x1e602000u : 0x1e202000u) | ((rm & 0x1fu) << 16) | - ((rn & 0x1fu) << 5); + return aa64_fcmp_reg(is_double, rn, rm); } static u32 aa_fneg(u32 is_double, u32 rd, u32 rn) { - return (is_double ? 0x1e614000u : 0x1e214000u) | ((rn & 0x1fu) << 5) | - (rd & 0x1fu); + return aa64_fp_dp1(is_double, AA64_FP_DP1_FNEG, rd, rn); } static u32 aa_fmov_fp(u32 is_double, u32 rd, u32 rn) { - return (is_double ? 0x1e604000u : 0x1e204000u) | ((rn & 0x1fu) << 5) | - (rd & 0x1fu); + return aa64_fp_dp1(is_double, AA64_FP_DP1_FMOV, rd, rn); } /* MOV Vd.16B, Vn.16B (alias of ORR Vd.16B, Vn.16B, Vn.16B): a full 128-bit @@ -535,65 +528,65 @@ static u32 aa_mov_vec16(u32 rd, u32 rn) { (rd & 0x1fu); } +/* The FP<->int conversion family: sf selects the GPR width (1=64-bit), ftype + * the FP width (1=double). Their roles flip between the convert-from-int + * (s/ucvtf: sf=src GPR, ftype=dst FP) and convert-to-int (fcvtz*: sf=dst GPR, + * ftype=src FP) directions, but the encoding is the same shape. */ static u32 aa_scvtf(u32 is_double_dst, u32 is64_src, u32 fd, u32 rn) { - return (is64_src ? 0x9e220000u : 0x1e220000u) | - (is_double_dst ? 0x00400000u : 0) | ((rn & 0x1fu) << 5) | (fd & 0x1fu); + return aa64_fp_int_cvt(is64_src, is_double_dst, AA64_FP_ICVT_SCVTF, fd, rn); } static u32 aa_ucvtf(u32 is_double_dst, u32 is64_src, u32 fd, u32 rn) { - return (is64_src ? 0x9e230000u : 0x1e230000u) | - (is_double_dst ? 0x00400000u : 0) | ((rn & 0x1fu) << 5) | (fd & 0x1fu); + return aa64_fp_int_cvt(is64_src, is_double_dst, AA64_FP_ICVT_UCVTF, fd, rn); } static u32 aa_fcvtzs(u32 is64_dst, u32 is_double_src, u32 rd, u32 fn) { - return (is64_dst ? 0x9e380000u : 0x1e380000u) | - (is_double_src ? 0x00400000u : 0) | ((fn & 0x1fu) << 5) | (rd & 0x1fu); + return aa64_fp_int_cvt(is64_dst, is_double_src, AA64_FP_ICVT_FCVTZS, rd, fn); } static u32 aa_fcvtzu(u32 is64_dst, u32 is_double_src, u32 rd, u32 fn) { - return (is64_dst ? 0x9e390000u : 0x1e390000u) | - (is_double_src ? 0x00400000u : 0) | ((fn & 0x1fu) << 5) | (rd & 0x1fu); + return aa64_fp_int_cvt(is64_dst, is_double_src, AA64_FP_ICVT_FCVTZU, rd, fn); } static u32 aa_fcvt_d_s(u32 rd, u32 rn) { - return 0x1e22c000u | ((rn & 0x1fu) << 5) | (rd & 0x1fu); + return aa64_fcvt_prec(/*src=single*/ 0, /*dst=double*/ 1, rd, rn); } static u32 aa_fcvt_s_d(u32 rd, u32 rn) { - return 0x1e624000u | ((rn & 0x1fu) << 5) | (rd & 0x1fu); + return aa64_fcvt_prec(/*src=double*/ 1, /*dst=single*/ 0, rd, rn); } +/* fmov between GPR and FP reg: the FP ftype tracks the operand width (64-bit + * GPR <-> double, 32-bit GPR <-> single), so sf and ftype move together. */ static u32 aa_fmov_gpr_to_fp(u32 is64, u32 fd, u32 rn) { - return (is64 ? 0x9e670000u : 0x1e270000u) | ((rn & 0x1fu) << 5) | - (fd & 0x1fu); + return aa64_fp_int_cvt(is64, is64, AA64_FP_ICVT_FMOV_TO_FP, fd, rn); } static u32 aa_fmov_fp_to_gpr(u32 is64, u32 rd, u32 fn) { - return (is64 ? 0x9e660000u : 0x1e260000u) | ((fn & 0x1fu) << 5) | - (rd & 0x1fu); + return aa64_fp_int_cvt(is64, is64, AA64_FP_ICVT_FMOV_TO_GPR, rd, fn); } static u32 aa_clz(u32 sf, u32 rd, u32 rn) { - return (sf ? 0xdac01000u : 0x5ac01000u) | ((rn & 0x1fu) << 5) | (rd & 0x1fu); + return aa64_dp1(sf, AA64_DP1_CLZ, rd, rn); } static u32 aa_rbit(u32 sf, u32 rd, u32 rn) { - return (sf ? 0xdac00000u : 0x5ac00000u) | ((rn & 0x1fu) << 5) | (rd & 0x1fu); + return aa64_dp1(sf, AA64_DP1_RBIT, rd, rn); } +/* REV reverses all bytes of the operand, so the 32-bit form is REV(32) and the + * 64-bit form is REV(64) — the opcode2 follows sf rather than being constant. */ static u32 aa_rev(u32 sf, u32 rd, u32 rn) { - return (sf ? 0xdac00c00u : 0x5ac00800u) | ((rn & 0x1fu) << 5) | (rd & 0x1fu); + return aa64_dp1(sf, sf ? AA64_DP1_REV64 : AA64_DP1_REV32, rd, rn); } static u32 aa_sbfm(u32 sf, u32 rd, u32 rn, u32 immr, u32 imms) { - return (sf ? 0x93400000u : 0x13000000u) | ((immr & 0x3fu) << 16) | - ((imms & 0x3fu) << 10) | ((rn & 0x1fu) << 5) | (rd & 0x1fu); + return aa64_bitfield(sf, /*SBFM*/ 0u, immr, imms, rd, rn); } static __attribute__((unused)) u32 aa_ubfm(u32 sf, u32 rd, u32 rn, u32 immr, u32 imms) { - return (sf ? 0xd3400000u : 0x53000000u) | ((immr & 0x3fu) << 16) | - ((imms & 0x3fu) << 10) | ((rn & 0x1fu) << 5) | (rd & 0x1fu); + return aa64_bitfield(sf, /*UBFM*/ 2u, immr, imms, rd, rn); } static __attribute__((unused)) u32 aa_ldaxr(u32 size, u32 rt, u32 rn) { diff --git a/src/arch/arch.h b/src/arch/arch.h @@ -102,7 +102,7 @@ typedef struct ArchDecodeOps { ArchInsnFormatter* (*formatter_new)(Compiler*); KitStatus (*format)(ArchInsnFormatter*, const KitDecodedInsn*, KitInsn* out); - void (*formatter_free)(ArchInsnFormatter*); + void (*formatter_destroy)(ArchInsnFormatter*); } ArchDecodeOps; typedef struct ArchEmuOps { diff --git a/src/arch/native_target.h b/src/arch/native_target.h @@ -614,6 +614,13 @@ static inline int native_loc_is_fp(NativeLoc loc) { /* Scalar size/align, clamped to a usable register-sized default. Shared by the * backends whose scalars are at most pointer-width (x64, rv64); aa64 keeps its * own size query because it asserts on over-wide scalars. */ +/* Round v up to the next multiple of align (a power of two, or 0 → no-op). + * Shared by every native backend's frame-layout math. */ +static inline u32 align_up_u32(u32 v, u32 align) { + u32 mask = align ? align - 1u : 0u; + return (v + mask) & ~mask; +} + static inline u32 native_type_size(NativeTarget* t, KitCgTypeId type) { u64 n = type ? cg_type_size(t->c, type) : 8u; if (n == 0) n = 8u; diff --git a/src/arch/riscv/disasm.c b/src/arch/riscv/disasm.c @@ -470,5 +470,5 @@ const ArchDecodeOps rv64_decode_ops = { .decode_block = rv64_decode_block, .formatter_new = rv64_formatter_new, .format = rv64_format_insn, - .formatter_free = rv64_formatter_destroy, + .formatter_destroy = rv64_formatter_destroy, }; diff --git a/src/arch/riscv/native.c b/src/arch/riscv/native.c @@ -52,7 +52,6 @@ enum { * fp, each up to 4 words for a far s0-relative offset) on top of the header, * sret, and variadic spills. Size the build buffer for the worst case. */ RV_KNOWN_PROLOGUE_WORDS = 192u, - RV_FRAME_SAVE_SIZE = 16u, }; /* s1..s11 (11) + fs0..fs11 (12); separate int/fp collect arrays use this cap. @@ -94,11 +93,6 @@ static int fits_i32(i64 v) { return v >= (i64)(i32)0x80000000 && v <= (i64)(i32)0x7fffffff; } -static u32 align_up_u32(u32 v, u32 align) { - u32 mask = align ? align - 1u : 0u; - return (v + mask) & ~mask; -} - static i64 floor_div_4096(i64 v) { if (v >= 0) return v / 4096; return -((-v + 4095) / 4096); @@ -610,7 +604,12 @@ static int rv_imm_legal(NativeTarget* t, NativeImmUse use, u32 op, return 0; } case NATIVE_IMM_CMP: - return imm == 0; /* compares need both ends in registers (SLT/branch) */ + /* Only the compare-against-zero case is folded; a non-zero RHS is + * materialized into a register first. This is a deliberate codegen + * simplification, not an architectural constraint — SLTI/SLTIU do + * accept a 12-bit signed immediate, but the comparison lowering does + * not special-case immediate operands beyond zero. */ + return imm == 0; case NATIVE_IMM_ADDR_OFFSET: return fits_i12(imm); } diff --git a/src/arch/wasm/abi.c b/src/arch/wasm/abi.c @@ -28,7 +28,7 @@ static void classify_void(ABIArgInfo* out) { } static void classify_scalar(TargetABI* a, KitCgTypeId t, ABIArgInfo* out) { - ABITypeInfo ti = abi_internal_type_info(a, t); + ABITypeInfo ti = abi_cg_type_info(a, t); ABIArgPart* parts; if (ti.size > 8) { @@ -76,7 +76,7 @@ static int try_classify_singleton(TargetABI* a, KitCgTypeId t, if (!field) return 0; if (field->kind == KIT_CG_TYPE_RECORD || field->kind == KIT_CG_TYPE_ARRAY) return 0; - ti = abi_internal_type_info(a, field_ty); + ti = abi_cg_type_info(a, field_ty); if (ti.size != (u32)ty->size) return 0; classify_scalar(a, field_ty, out); return 1; @@ -84,7 +84,7 @@ static int try_classify_singleton(TargetABI* a, KitCgTypeId t, static void classify_aggregate(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, int is_return) { - ABITypeInfo ti = abi_internal_type_info(a, t); + ABITypeInfo ti = abi_cg_type_info(a, t); if (ti.size == 0) { classify_void(out); return; diff --git a/src/arch/wasm/emit.c b/src/arch/wasm/emit.c @@ -203,6 +203,7 @@ static u32 reg_local(WTarget* t, Reg r, KitCgTypeId ty, RegClass cls) { t->reg_to_local[r] = add_wasm_local(t, vt); t->reg_type[r] = ty; t->reg_cls[r] = (u8)cls; + if (r + 1u > t->reg_hwm) t->reg_hwm = r + 1u; } return t->reg_to_local[r]; } @@ -603,8 +604,11 @@ void wasm_func_begin(CGTarget* tg, const CGFuncDesc* d) { t->va_arg_tmp_addr_local = 0xffffffffu; t->nparams_cg = 0; t->nbyval_copies = 0; - /* Wipe reg map. */ - for (u32 i = 0; i < t->reg_cap; ++i) t->reg_to_local[i] = 0xffffffffu; + /* Wipe reg map. Only [0, reg_hwm) can hold a non-sentinel binding from a + * prior function — reg_cap grows monotonically but slots beyond the + * high-water mark are sentinel-initialized at grow time and never bound, so + * wiping the full reg_cap each function is wasted work on large TUs. */ + for (u32 i = 0; i < t->reg_hwm; ++i) t->reg_to_local[i] = 0xffffffffu; idx = sym_to_wasm_func(t, d->sym, &f); t->cur_func_idx = idx; @@ -1077,21 +1081,6 @@ static void promote_import_func(WTarget* t, ObjSymId sym, WasmFunc* f, } } -const char* wasm_tail_call_unrealizable_reason(CGTarget* tg, - const CGCallDesc* d) { - (void)tg; - /* Variadic tail calls are not realizable on wasm: varargs are packed into a - * buffer carved from this function's linear-memory frame, which return_call - * tears down before the callee reads it. sret is realizable — the tail - * forwards the function's own incoming sret pointer (see wasm_call). wasm - * function parameters are wasm locals, so there is no caller stack-arg area - * to overflow. */ - if (d->abi && d->abi->variadic) - return "wasm cannot tail-call a variadic function (its vararg buffer " - "lives in the frame a sibling call tears down)"; - return NULL; -} - void wasm_call(CGTarget* tg, const CGCallDesc* d) { WTarget* t = (WTarget*)tg; if (t->dead) return; @@ -1104,9 +1093,9 @@ void wasm_call(CGTarget* tg, const CGCallDesc* d) { int callee_variadic = (d->abi && d->abi->variadic) ? 1 : 0; int is_tail = (d->flags & CG_CALL_TAIL) ? 1 : 0; if (is_tail) { - /* Realizability is decided by CG via wasm_tail_call_unrealizable_reason - * before CG_CALL_TAIL is set: variadic tails are rejected there, and sret - * tails forward the incoming sret pointer (handled in the WIR emit). */ + /* Realizability is decided by CG via wasm_ir_tail_call_unrealizable_reason + * (target.c) before CG_CALL_TAIL is set: variadic tails are rejected there, + * and sret tails forward the incoming sret pointer (handled in WIR emit). */ ensure_module(t); t->module->features |= WASM_FEATURE_TAIL_CALLS; } diff --git a/src/arch/wasm/internal.h b/src/arch/wasm/internal.h @@ -292,11 +292,14 @@ typedef struct WTarget { struct WasmFunc* cur_func; /* SSA Reg -> Wasm local index (0..nparams=params, then locals). 0xffffffffu - * means "not assigned yet". */ + * means "not assigned yet". reg_cap grows monotonically across the TU; + * reg_hwm tracks one past the highest Reg actually bound in any function so + * far, so func_begin only needs to wipe [0, reg_hwm) rather than reg_cap. */ u32* reg_to_local; KitCgTypeId* reg_type; u8* reg_cls; u32 reg_cap; + u32 reg_hwm; /* WIR record list for the current function. */ WIR* wir; diff --git a/src/arch/x64/emit.h b/src/arch/x64/emit.h @@ -68,10 +68,6 @@ static inline u32 type_byte_size(KitCgTypeId t) { if (t == CG_BUILTIN_ID(KIT_CG_BUILTIN_F128)) return 16; return 8; } -static inline int type_is_signed(KitCgTypeId t) { - (void)t; - return 0; -} static inline void x64_abi_direct_reg_need(const ABIArgInfo* ai, u32* need_int, u32* need_fp) { diff --git a/src/arch/x64/native.c b/src/arch/x64/native.c @@ -142,11 +142,6 @@ static X64NativeSlot* x64_slot_get(X64NativeTarget* a, NativeFrameSlot fs) { return native_frame_slot_at(&a->frame, fs); } -static u32 align_up_u32(u32 v, u32 align) { - u32 mask = align ? align - 1u : 0u; - return (v + mask) & ~mask; -} - /* ============================ type helpers ============================ */ /* Scalar size/align/mem/class/loc constructors are shared in native_target.h diff --git a/src/asm/asm.c b/src/asm/asm.c @@ -504,9 +504,16 @@ int asm_driver_eat_punct(AsmDriver* d, u32 p) { } void asm_driver_expect_punct(AsmDriver* d, u32 p, const char* what) { - if (!asm_driver_eat_punct(d, p)) - d_panicf(d, "asm: expected '%.*s' (%.*s)", SLICE_ARG(SLICE_LIT("punct")), + if (!asm_driver_eat_punct(d, p)) { + /* Single-char punctuators (incl. '#') carry their ASCII value in `p`; + * print it directly. Multi-char puncts fall back to the description. */ + char ch = (char)p; + if (p >= 0x20 && p < 0x7f) + d_panicf(d, "asm: expected '%c' (%.*s)", ch, + SLICE_ARG(slice_from_cstr(what))); + d_panicf(d, "asm: expected punctuator (%.*s)", SLICE_ARG(slice_from_cstr(what))); + } } i64 asm_driver_parse_const(AsmDriver* d) { @@ -530,7 +537,11 @@ int asm_driver_tok_is_punct(AsmTok t, u32 p) { /* ---- string-literal decoding ---- */ -static void decode_string(AsmDriver* d, Sym spelling, u8** out, u32* nout) { +/* Decode a string literal's spelling into raw bytes. *out is the heap buffer + * (allocated with size *cap_out, which the caller must pass back to free — + * the decoded length *nout <= *cap_out once escapes collapse). */ +static void decode_string(AsmDriver* d, Sym spelling, u8** out, u32* nout, + size_t* cap_out) { size_t n = 0; const char* p = asm_str(d, spelling, &n); /* Skip any encoding prefix (L/u/u8/U). */ @@ -540,8 +551,8 @@ static void decode_string(AsmDriver* d, Sym spelling, u8** out, u32* nout) { } if (n < 2 || p[0] != '"' || p[n - 1] != '"') d_panicf(d, "asm: malformed string literal"); - size_t cap = n; - u8* buf = (u8*)d->heap->alloc(d->heap, cap ? cap : 1, 1); + size_t cap = n ? n : 1; + u8* buf = (u8*)d->heap->alloc(d->heap, cap, 1); u32 k = 0; for (size_t i = 1; i + 1 < n; ++i) { char c = p[i]; @@ -627,6 +638,7 @@ static void decode_string(AsmDriver* d, Sym spelling, u8** out, u32* nout) { } *out = buf; *nout = k; + *cap_out = cap; } /* ---- directives ---- */ @@ -1100,11 +1112,12 @@ static void do_directive(AsmDriver* d, Sym name) { (void)d_next(d); u8* buf = NULL; u32 n = 0; - decode_string(d, t.spelling, &buf, &n); + size_t cap = 0; + decode_string(d, t.spelling, &buf, &n, &cap); (void)asm_driver_cur_section(d); d->mc->emit_bytes(d->mc, buf, n); if (term) emit_le(d, 0, 1); - d->heap->free(d->heap, buf, n); + d->heap->free(d->heap, buf, cap); if (!asm_driver_eat_comma(d)) break; } d_skip_to_eol(d); @@ -1289,7 +1302,7 @@ static void relax_local_branches(AsmDriver* d) { for (i = 0; i < total; ++i) { const Reloc* r = obj_reloc_at(d->ob, i); const ObjSym* tgt; - Section* sec; + const Section* sec; u8 insn[4]; if (!r || r->removed) continue; if (!is_relaxable_branch_kind(r->kind)) continue; @@ -1298,14 +1311,21 @@ static void relax_local_branches(AsmDriver* d) { if (tgt->section_id != r->section_id) continue; /* cross-section / undef */ if (tgt->bind != SB_LOCAL) continue; /* preemptible; keep */ if (tgt->kind == SK_FUNC) continue; /* call/tail-call; keep */ - sec = (Section*)obj_section_get(d->ob, r->section_id); + /* Read-only access to the in-progress section is fine via the const + * accessor; the byte write goes back through obj_patch so we don't reach + * into Section internals. */ + sec = obj_section_get(d->ob, r->section_id); if (!sec) continue; if ((u64)r->offset + 4 > sec->bytes.total) continue; buf_read(&sec->bytes, r->offset, insn, 4); /* Section-relative S and P make the base cancel: disp = S + A - P. */ link_reloc_apply(d->c, (RelocKind)r->kind, insn, tgt->value, r->addend, r->offset); - buf_patch(&sec->bytes, r->offset, insn, 4); + obj_patch(d->ob, r->section_id, r->offset, insn, 4); + /* No public per-reloc tombstone setter exists on ObjBuilder; the const + * cast mutates the still-private in-progress object we own outright (we + * built every reloc in this builder). Tolerated until obj exposes an + * obj_reloc_remove() mutator alongside obj_section/symbol_remove. */ ((Reloc*)r)->removed = 1; } } diff --git a/src/asm/asm_lex.c b/src/asm/asm_lex.c @@ -149,11 +149,6 @@ void asm_lex_close(AsmLexer* l) { SrcLoc asm_lex_loc(const AsmLexer* l) { return asm_lex_here(l); } u32 asm_lex_file_id(const AsmLexer* l) { return l->file_id; } -const AsmLitInfo* asm_lex_lit(const AsmLexer* l, AsmLitId id) { - (void)l; - (void)id; - return NULL; -} /* Intern bytes [start, end) with line splices (\<newline>) removed, so token * spellings reflect post-phase-2 logical text. */ diff --git a/src/asm/asm_lex.h b/src/asm/asm_lex.h @@ -58,38 +58,11 @@ typedef enum AsmPunct { ASM_P_HASH_HASH, } AsmPunct; -typedef u32 AsmLitId; -#define ASM_LIT_NONE 0u - -typedef enum AsmLitKind { - ASM_LIT_INT, - ASM_LIT_FLOAT, - ASM_LIT_STRING, - ASM_LIT_CHAR, -} AsmLitKind; - -typedef enum AsmLitEnc { - ASM_LENC_ORDINARY, - ASM_LENC_UTF8, - ASM_LENC_WIDE, - ASM_LENC_UTF16, - ASM_LENC_UTF32, -} AsmLitEnc; - -typedef struct AsmLitInfo { - u8 kind; - u8 enc; - u16 flags; - Sym spelling; - BytesId bytes; -} AsmLitInfo; - typedef struct AsmTok { u16 kind; u16 flags; SrcLoc loc; Sym spelling; - AsmLitId lit; union { Sym ident; Sym str; @@ -106,6 +79,5 @@ void asm_lex_close(AsmLexer*); AsmTok asm_lex_next(AsmLexer*); SrcLoc asm_lex_loc(const AsmLexer*); u32 asm_lex_file_id(const AsmLexer*); -const AsmLitInfo* asm_lex_lit(const AsmLexer*, AsmLitId); #endif diff --git a/src/cg/arith.c b/src/cg/arith.c @@ -268,8 +268,6 @@ void api_cg_convert_kind(KitCg* g, KitCgTypeId dst_type, ConvKind ck) { agg.size = 16; agg.align = 16; g->target->copy_bytes(g->target, dst_addr, src_addr, agg); - api_release_temp_local(g, dst_addr.v.local); - api_release_temp_local(g, src_addr.v.local); } else if (v.op.kind == OPK_LOCAL) { g->target->store(g->target, dst_lv, v.op, api_mem_for_lvalue(g, &dst_lv, sty)); @@ -1046,7 +1044,6 @@ int api_try_i128_convert(KitCg* g, ConvKind ck, KitCgTypeId sty, if (dw == 0) return 0; /* i128->float unsupported here */ addr = api_i128_addr(g, v); lo = api_i128_load_lane(g, addr, lo_off); - api_release_temp_local(g, addr.v.local); api_release(g, v); if (dw >= 64) { api_push(g, api_make_sv(lo, dty)); diff --git a/src/cg/call.c b/src/cg/call.c @@ -86,12 +86,6 @@ CGLocal api_alloc_call_result(KitCg* g, KitCgTypeId ret_ty) { return api_alloc_temp_local(g, ret_ty); } -void api_release_call_args(KitCg* g, CGLocal* args, u32 nargs) { - for (u32 i = 0; i < nargs; ++i) { - if (args[i] != CG_LOCAL_NONE) api_release_temp_local(g, args[i]); - } -} - void api_push_call_result(KitCg* g, CGLocal result, KitCgTypeId ret_ty) { Operand op = api_op_local(result, ret_ty); /* An aggregate result is a PLACE (it is addressed/copied, never a scalar @@ -136,17 +130,11 @@ static int api_tail_decide(KitCg* g, const CGCallDesc* desc, return 0; } -static void api_finish_call(KitCg* g, CGCallDesc* desc, CGLocal* args, - u32 nargs, Operand callee_op, ApiSValue* callee, - int want_tail, int emit_tail) { - if (emit_tail) api_temp_locals_finish(g); +static void api_finish_call(KitCg* g, CGCallDesc* desc, int want_tail, + int emit_tail) { if (!emit_tail) api_call_clobber_boundary(g, desc); g->target->call(g->target, desc); - api_release_call_args(g, args, nargs); - if (callee && callee->op.kind != OPK_GLOBAL) { - api_release_temp_local(g, callee_op.v.local); - } /* Push the single result (if any) onto the stack. */ if (desc->result != CG_LOCAL_NONE) { KitCgTypeId rty = cg_type_func_result_id(g->c, desc->fn_type); @@ -215,8 +203,7 @@ void kit_cg_call(KitCg* g, uint32_t nargs, KitCgTypeId fn_type, desc.result = api_alloc_call_result(g, result_type); (void)T; - api_finish_call(g, &desc, args, nargs, callee_op, &callee, want_tail, - emit_tail); + api_finish_call(g, &desc, want_tail, emit_tail); } void api_call_symbol_common(KitCg* g, KitCgSym sym, uint32_t nargs, @@ -264,7 +251,7 @@ void api_call_symbol_common(KitCg* g, KitCgSym sym, uint32_t nargs, result_type = emit_tail ? KIT_CG_TYPE_NONE : cg_type_func_result_id(g->c, fty); if (result_type != KIT_CG_TYPE_NONE) desc.result = api_alloc_call_result(g, result_type); - api_finish_call(g, &desc, args, nargs, callee_op, NULL, want_tail, emit_tail); + api_finish_call(g, &desc, want_tail, emit_tail); } void kit_cg_call_symbol(KitCg* g, KitCgSym sym, uint32_t nargs, diff --git a/src/cg/control.c b/src/cg/control.c @@ -1031,7 +1031,6 @@ void kit_cg_field(KitCg* g, uint32_t field_index) { result = api_op_local(fr, rec_ptr_ty); T->binop(T, BO_IADD, result, base_addr, api_op_imm((i64)field_offset, rec_ptr_ty)); - api_release_temp_local(g, base_addr.v.local); } api_push( g, api_make_lv(api_op_indirect(result.v.local, 0, field_ty), field_ty)); diff --git a/src/cg/fold.c b/src/cg/fold.c @@ -165,14 +165,7 @@ CmpOp api_invert_cmp(CmpOp op) { } void api_release_cmp(KitCg* g, ApiSValue* sv) { - if (sv->delayed.cmp.a_owned) api_release_operand_local(g, sv->delayed.cmp.a); - if (sv->delayed.cmp.b_owned && - (sv->delayed.cmp.b.kind != OPK_LOCAL || - sv->delayed.cmp.a.kind != OPK_LOCAL || - sv->delayed.cmp.b.v.local != sv->delayed.cmp.a.v.local || - !sv->delayed.cmp.a_owned)) { - api_release_operand_local(g, sv->delayed.cmp.b); - } + (void)g; memset(&sv->delayed.cmp.a, 0, sizeof sv->delayed.cmp.a); memset(&sv->delayed.cmp.b, 0, sizeof sv->delayed.cmp.b); sv->delayed.cmp.a_owned = 0; @@ -183,14 +176,6 @@ void api_release_cmp(KitCg* g, ApiSValue* sv) { void api_materialize_cmp_to(KitCg* g, ApiSValue* sv, Operand dst) { g->target->cmp(g->target, sv->delayed.cmp.op, dst, sv->delayed.cmp.a, sv->delayed.cmp.b); - if (sv->delayed.cmp.a_owned && sv->delayed.cmp.a.kind == OPK_LOCAL && - sv->delayed.cmp.a.v.local != dst.v.local) { - api_release_operand_local(g, sv->delayed.cmp.a); - } - if (sv->delayed.cmp.b_owned && sv->delayed.cmp.b.kind == OPK_LOCAL && - sv->delayed.cmp.b.v.local != dst.v.local) { - api_release_operand_local(g, sv->delayed.cmp.b); - } memset(&sv->delayed.cmp.a, 0, sizeof sv->delayed.cmp.a); memset(&sv->delayed.cmp.b, 0, sizeof sv->delayed.cmp.b); sv->delayed.cmp.a_owned = 0; @@ -245,15 +230,7 @@ ApiSValue api_make_arith_binop(BinOp op, Operand a, Operand b, KitCgTypeId ty, } void api_release_arith(KitCg* g, ApiSValue* sv) { - if (sv->delayed.arith.a_owned) - api_release_operand_local(g, sv->delayed.arith.a); - if (sv->delayed.arith.b_owned && - (sv->delayed.arith.b.kind != OPK_LOCAL || - sv->delayed.arith.a.kind != OPK_LOCAL || - sv->delayed.arith.b.v.local != sv->delayed.arith.a.v.local || - !sv->delayed.arith.a_owned)) { - api_release_operand_local(g, sv->delayed.arith.b); - } + (void)g; memset(&sv->delayed.arith.a, 0, sizeof sv->delayed.arith.a); memset(&sv->delayed.arith.b, 0, sizeof sv->delayed.arith.b); sv->delayed.arith.a_owned = 0; @@ -269,14 +246,6 @@ void api_materialize_arith_to(KitCg* g, ApiSValue* sv, Operand dst) { g->target->binop(g->target, sv->delayed.arith.bin_op, dst, sv->delayed.arith.a, sv->delayed.arith.b); } - if (sv->delayed.arith.a_owned && sv->delayed.arith.a.kind == OPK_LOCAL && - sv->delayed.arith.a.v.local != dst.v.local) { - api_release_operand_local(g, sv->delayed.arith.a); - } - if (sv->delayed.arith.b_owned && sv->delayed.arith.b.kind == OPK_LOCAL && - sv->delayed.arith.b.v.local != dst.v.local) { - api_release_operand_local(g, sv->delayed.arith.b); - } memset(&sv->delayed.arith.a, 0, sizeof sv->delayed.arith.a); memset(&sv->delayed.arith.b, 0, sizeof sv->delayed.arith.b); sv->delayed.arith.a_owned = 0; diff --git a/src/cg/internal.h b/src/cg/internal.h @@ -258,7 +258,6 @@ void kit_cg_atomic_fence(KitCg* g, KitCgMemOrder order); CGLocal* api_alloc_call_args(KitCg* g, u32 nargs); void api_pack_call_arg(KitCg* g, CGLocal* out, KitCgTypeId fty, u32 idx); CGLocal api_alloc_call_result(KitCg* g, KitCgTypeId ret_ty); -void api_release_call_args(KitCg* g, CGLocal* args, u32 nargs); void api_push_call_result(KitCg* g, CGLocal result, KitCgTypeId ret_ty); void kit_cg_call(KitCg* g, uint32_t nargs, KitCgTypeId fn_type, KitCgCallAttrs attrs); @@ -412,10 +411,7 @@ ApiSValue api_pop(KitCg* g); CGLocal api_local_of_sv(const ApiSValue* sv); void api_set_owned_local(ApiSValue* sv, CGLocal r); KitCgTypeId api_owned_local_type(KitCg* g, const ApiSValue* sv); -void api_temp_locals_begin(KitCg* g); -void api_temp_locals_finish(KitCg* g); CGLocal api_alloc_temp_local(KitCg* g, KitCgTypeId ty); -void api_release_temp_local(KitCg* g, CGLocal r); MemAccess api_mem_for_lvalue(KitCg* g, const Operand* lv, KitCgTypeId ty); MemAccess api_mem_from_access(KitCg* g, const Operand* lv, KitCgMemAccess access); @@ -426,7 +422,6 @@ void api_require_scalar_mem_type(KitCg* g, const char* who, KitCgTypeId ty); void api_require_pointer_value(KitCg* g, const char* who, KitCgTypeId ty); void api_validate_memory_value(KitCg* g, const char* who, KitCgTypeId access_ty, KitCgTypeId value_ty); -void api_release_operand_local(KitCg* g, Operand op); int api_sv_owns_operand_local(const ApiSValue* sv, const Operand* op); void api_ensure_local(KitCg* g, ApiSValue* sv); Operand api_force_local(KitCg* g, ApiSValue* v, KitCgTypeId ty); diff --git a/src/cg/memory.c b/src/cg/memory.c @@ -236,7 +236,6 @@ void kit_cg_load(KitCg* g, KitCgMemAccess access) { CgTarget* T; KitCgTypeId ty; KitCgTypeId access_ty; - CGLocal owned_base = CG_LOCAL_NONE; Operand mem_op; CGLocal dst_r; Operand dst; @@ -304,7 +303,6 @@ void kit_cg_load(KitCg* g, KitCgMemAccess access) { KitCgTypeId pty = cg_type_ptr_to(g->c, api_sv_type(&base)); Operand addr = api_lvalue_addr(g, &base, pty); mem_op = api_op_indirect(addr.v.local, 0, access_ty); - owned_base = addr.v.local; } else { mem_op = place_operand_for_access(base.op, access_ty); } @@ -324,7 +322,6 @@ void kit_cg_load(KitCg* g, KitCgMemAccess access) { } api_release(g, &base); - if (owned_base != CG_LOCAL_NONE) api_release_temp_local(g, owned_base); api_push(g, api_make_sv(dst, access_ty)); } @@ -390,7 +387,6 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { KitCgTypeId ty; KitCgTypeId access_ty; Operand src; - CGLocal owned_base = CG_LOCAL_NONE; Operand mem_op; int is_lvalue; int is_bitfield; @@ -418,8 +414,6 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { cg_type_is_aggregate(g->c, api_sv_type(&rv)))) { KitCgTypeId ptr_ty; Operand dst_addr, src_addr; - int dst_addr_owned; - int src_addr_owned; int src_ptr_rvalue; AggregateAccess agg; u32 src_size; @@ -452,20 +446,15 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { } ptr_ty = cg_type_ptr_to(g->c, ty); dst_addr = api_lvalue_addr(g, &base, ptr_ty); - dst_addr_owned = 1; if (src_ptr_rvalue) { src_addr = api_force_local(g, &rv, api_sv_type(&rv)); - src_addr_owned = 0; } else { src_addr = api_lvalue_addr(g, &rv, ptr_ty); - src_addr_owned = 1; } memset(&agg, 0, sizeof agg); agg.size = access_size; agg.align = access.align ? access.align : abi_cg_alignof(g->c->abi, ty); T->copy_bytes(T, dst_addr, src_addr, agg); - if (dst_addr_owned) api_release_temp_local(g, dst_addr.v.local); - if (src_addr_owned) api_release_temp_local(g, src_addr.v.local); api_release(g, &base); api_release(g, &rv); return; @@ -526,7 +515,6 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { KitCgTypeId pty = cg_type_ptr_to(g->c, api_sv_type(&base)); Operand addr = api_lvalue_addr(g, &base, pty); mem_op = api_op_indirect(addr.v.local, 0, access_ty); - owned_base = addr.v.local; } else { mem_op = place_operand_for_access(base.op, access_ty); } @@ -555,7 +543,6 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { api_release(g, &base); api_release(g, &rv); - if (owned_base != CG_LOCAL_NONE) api_release_temp_local(g, owned_base); } /* ============================================================ diff --git a/src/cg/session.c b/src/cg/session.c @@ -238,13 +238,11 @@ KitStatus kit_cg_detach(KitCg* g) { return KIT_OK; } -KitStatus kit_cg_abort(KitCg* g) { return kit_cg_detach(g); } - void kit_cg_free(KitCg* g) { Heap* h; if (!g) return; h = g->c->ctx->heap; - (void)kit_cg_abort(g); + (void)kit_cg_detach(g); h->free(h, g, sizeof *g); } @@ -408,7 +406,6 @@ void kit_cg_func_begin_attrs(KitCg* g, KitCgSym cg_sym, if (dt != DEBUG_TYPE_NONE) debug_func_begin(g->debug, sym, dt, g->cur_loc); } T->func_begin(T, &g->fn_desc); - api_temp_locals_begin(g); } void kit_cg_func_begin(KitCg* g, KitCgSym cg_sym) { @@ -469,7 +466,6 @@ static void api_debug_emit_source_locals(KitCg* g) { void kit_cg_func_end(KitCg* g) { if (!g) return; - api_temp_locals_finish(g); g->target->func_end(g->target); api_debug_emit_source_locals(g); if (g->debug) debug_func_end(g->debug); diff --git a/src/cg/value.c b/src/cg/value.c @@ -241,10 +241,6 @@ KitCgTypeId api_owned_local_type(KitCg* g, const ApiSValue* sv) { /* ---- temporary local allocation ---- */ -void api_temp_locals_begin(KitCg* g) { (void)g; } - -void api_temp_locals_finish(KitCg* g) { (void)g; } - CGLocal api_alloc_temp_local(KitCg* g, KitCgTypeId ty) { CGLocalDesc d; CGLocal local; @@ -268,11 +264,6 @@ CGLocal api_alloc_temp_local(KitCg* g, KitCgTypeId ty) { return local; } -void api_release_temp_local(KitCg* g, CGLocal r) { - (void)g; - (void)r; -} - MemAccess api_mem_for_lvalue(KitCg* g, const Operand* lv, KitCgTypeId ty) { MemAccess m; memset(&m, 0, sizeof m); @@ -376,10 +367,6 @@ void api_validate_memory_value(KitCg* g, const char* who, KitCgTypeId access_ty, } } -void api_release_operand_local(KitCg* g, Operand op) { - if (op.kind == OPK_LOCAL) api_release_temp_local(g, op.v.local); -} - int api_sv_owns_operand_local(const ApiSValue* sv, const Operand* op) { return sv->res == RES_LOCAL && op->kind == OPK_LOCAL && sv->op.kind == OPK_LOCAL && sv->op.v.local == op->v.local; @@ -441,9 +428,6 @@ Operand api_force_local(KitCg* g, ApiSValue* v, KitCgTypeId ty) { T->load_imm(T, dst, v->op.v.imm); } else if (api_is_lvalue_sv(v)) { T->load(T, dst, v->op, api_mem_for_lvalue(g, &v->op, ty)); - if (v->op.kind == OPK_INDIRECT) { - api_release_temp_local(g, v->op.v.ind.base); - } } else if (v->op.kind == OPK_GLOBAL) { T->addr_of(T, dst, v->op); } else { @@ -464,8 +448,6 @@ void api_release(KitCg* g, ApiSValue* sv) { api_release_cmp(g, sv); } else if (sv->kind == SV_ARITH) { api_release_arith(g, sv); - } else if (sv->res == RES_LOCAL) { - api_release_temp_local(g, (CGLocal)api_local_of_sv(sv)); } sv->res = RES_INHERENT; } diff --git a/src/cg/wide.c b/src/cg/wide.c @@ -64,7 +64,6 @@ void api_store_f128_bytes(KitCg* g, CGLocal local, KitCgTypeId ty, g->target->store( g->target, api_op_indirect(ar, 8, i64_ty), api_op_imm((i64)api_u64_from_target_bytes(g, bytes + 8), i64_ty), ma); - api_release_temp_local(g, ar); } void api_encode_binary128_from_double(KitCg* g, double value, u8 out[16]) { @@ -178,7 +177,6 @@ ApiSValue api_make_wide8_const_bits(KitCg* g, u64 bits, KitCgTypeId ty) { api_op_imm((i64)(i32)(u32)(bits & 0xffffffffu), i32_ty), ma); g->target->store(g->target, api_op_indirect(ar, api_wide8_hi_off(g), i32_ty), api_op_imm((i64)(i32)(u32)(bits >> 32), i32_ty), ma); - api_release_temp_local(g, ar); return api_make_sv(api_op_local(local, ty), ty); } @@ -274,8 +272,6 @@ ApiSValue api_wide16_materialize_lvalue(KitCg* g, ApiSValue* v, agg.size = 16; agg.align = 16; g->target->copy_bytes(g->target, dst_addr, src_addr, agg); - api_release_temp_local(g, dst_addr.v.local); - api_release_temp_local(g, src_addr.v.local); return api_make_lv(dst_lv, ty); } if (v->op.kind == OPK_LOCAL) { diff --git a/src/debug/debug.c b/src/debug/debug.c @@ -80,14 +80,6 @@ void debug_free(Debug* d) { d->heap->free(d->heap, d->types, sizeof(*d->types) * d->types_cap); if (d->files) d->heap->free(d->heap, d->files, sizeof(*d->files) * d->files_cap); - if (d->loclists) { - for (i = 0; i < d->nloclists; ++i) { - DebugLocList* l = &d->loclists[i]; - if (l->entries) - d->heap->free(d->heap, l->entries, sizeof(*l->entries) * l->cap); - } - d->heap->free(d->heap, d->loclists, sizeof(*d->loclists) * d->loclists_cap); - } U32ToU32_fini(&d->src_to_file); d->heap->free(d->heap, d, sizeof(*d)); } @@ -513,29 +505,3 @@ void debug_line(Debug* d, ObjSecId text_section_id, u32 text_offset, SrcLoc loc, row->loc = loc; row->is_stmt = (u8)(is_stmt ? 1 : 0); } - -/* ---- loclists (Phase 5 placeholder) ---- */ - -u32 debug_loclist_new(Debug* d) { - DebugLocList* l; - if (VEC_GROW(d->heap, d->loclists, d->loclists_cap, d->nloclists + 1)) - debug_oom(d, "loclists"); - l = &d->loclists[d->nloclists]; - memset(l, 0, sizeof(*l)); - d->nloclists++; - return d->nloclists; -} - -void debug_loclist_add(Debug* d, u32 id, u32 begin_pc, u32 end_pc, - DebugVarLoc vloc) { - DebugLocList* l; - DebugLocListEntry* e; - if (id == 0 || id > d->nloclists) return; - l = &d->loclists[id - 1]; - if (VEC_GROW(d->heap, l->entries, l->cap, l->nentries + 1)) - debug_oom(d, "loclist entries"); - e = &l->entries[l->nentries++]; - e->begin_pc = begin_pc; - e->end_pc = end_pc; - e->loc = vloc; -} diff --git a/src/debug/debug.h b/src/debug/debug.h @@ -108,7 +108,6 @@ typedef enum DebugVarLocKind { DVL_FRAME, DVL_REG, DVL_GLOBAL, - DVL_LOCLIST, /* time-varying location, see debug_loclist_* */ } DebugVarLocKind; typedef struct DebugVarLoc { @@ -118,7 +117,6 @@ typedef struct DebugVarLoc { i32 frame_ofs; Reg reg; ObjSymId global; - u32 loclist_id; } v; } DebugVarLoc; @@ -139,10 +137,6 @@ void debug_set_pending_loc(Debug*, SrcLoc); * (section, offset, loc) triples. */ void debug_emit_row(Debug*, ObjSecId text_section_id, u32 text_offset, SrcLoc); -/* location lists — for opt'd code where a variable moves between locations */ -u32 debug_loclist_new(Debug*); -void debug_loclist_add(Debug*, u32 id, u32 begin_pc, u32 end_pc, DebugVarLoc); - /* Emit the accumulated debug info as DWARF sections into the ObjBuilder. * Must be called after all code sections are finalized but before the * file emitters run. */ diff --git a/src/debug/debug_emit.c b/src/debug/debug_emit.c @@ -33,6 +33,7 @@ void abbrev_fini_heap(DebugAbbrevPool* p, Heap* h); typedef struct StrTab { Buf buf; SymToU32 by_sym; /* Sym → byte offset within buf */ + SymToU32 idx_by_sym; /* Sym → insertion index (== DW_FORM_strx value) */ /* Insertion order — used to populate .debug_str_offsets. */ Sym* syms; u32 nsyms; @@ -42,6 +43,7 @@ typedef struct StrTab { static void str_init(StrTab* s, Heap* h) { buf_init(&s->buf, h); SymToU32_init(&s->by_sym, h); + SymToU32_init(&s->idx_by_sym, h); s->syms = NULL; s->nsyms = 0; s->syms_cap = 0; @@ -50,6 +52,7 @@ static void str_init(StrTab* s, Heap* h) { static void str_fini(StrTab* s, Heap* h) { buf_fini(&s->buf); SymToU32_fini(&s->by_sym); + SymToU32_fini(&s->idx_by_sym); if (s->syms) h->free(h, s->syms, sizeof(Sym) * s->syms_cap); s->syms = NULL; s->nsyms = 0; @@ -77,16 +80,14 @@ static u32 str_intern(StrTab* s, Heap* h, Pool* pool, Sym sym) { } SymToU32_set(&s->by_sym, sym, ofs); if (VEC_GROW(h, s->syms, s->syms_cap, s->nsyms + 1)) return ofs; + SymToU32_set(&s->idx_by_sym, sym, s->nsyms); s->syms[s->nsyms++] = sym; return ofs; } static u32 str_index_of(StrTab* s, Sym sym) { - u32 i; - for (i = 0; i < s->nsyms; ++i) { - if (s->syms[i] == sym) return i; - } - return 0; + u32* idx = SymToU32_get(&s->idx_by_sym, sym); + return idx ? *idx : 0; } /* ---------------------------------------------------------------- */ @@ -214,44 +215,14 @@ static void add_fixup(EmitCtx* e, u32 buf_offset, DebugTypeId target) { fx->target = target; } -static void add_info_reloc(EmitCtx* e, u32 buf_offset, ObjSymId sym) { +/* Append a symbol-relative address reloc to one of the per-section AddrReloc + * arrays (info/line/aranges/rng). The arrays share an identical element + * layout and growth policy; only the backing array + count/cap differ. */ +static void add_addr_reloc(EmitCtx* e, AddrReloc** arr, u32* n, u32* cap, + u32 buf_offset, ObjSymId sym) { AddrReloc* r; - if (VEC_GROW(e->heap, e->info_relocs, e->info_relocs_cap, - e->ninfo_relocs + 1)) - return; - r = &e->info_relocs[e->ninfo_relocs++]; - r->buf_offset = buf_offset; - r->sym = sym; - r->section = OBJ_SEC_NONE; -} - -static void add_line_reloc(EmitCtx* e, u32 buf_offset, ObjSymId sym) { - AddrReloc* r; - if (VEC_GROW(e->heap, e->line_relocs, e->line_relocs_cap, - e->nline_relocs + 1)) - return; - r = &e->line_relocs[e->nline_relocs++]; - r->buf_offset = buf_offset; - r->sym = sym; - r->section = OBJ_SEC_NONE; -} - -static void add_aranges_reloc(EmitCtx* e, u32 buf_offset, ObjSymId sym) { - AddrReloc* r; - if (VEC_GROW(e->heap, e->aranges_relocs, e->aranges_relocs_cap, - e->naranges_relocs + 1)) - return; - r = &e->aranges_relocs[e->naranges_relocs++]; - r->buf_offset = buf_offset; - r->sym = sym; - r->section = OBJ_SEC_NONE; -} - -static void add_rng_reloc(EmitCtx* e, u32 buf_offset, ObjSymId sym) { - AddrReloc* r; - if (VEC_GROW(e->heap, e->rng_relocs, e->nrng_relocs_cap, e->nrng_relocs + 1)) - return; - r = &e->rng_relocs[e->nrng_relocs++]; + if (VEC_GROW(e->heap, *arr, *cap, *n + 1)) return; + r = &(*arr)[(*n)++]; r->buf_offset = buf_offset; r->sym = sym; r->section = OBJ_SEC_NONE; @@ -601,9 +572,6 @@ static void emit_var_loc_exprloc(EmitCtx* e, Buf* b, DebugVarLoc loc) { (void)loc.v.global; break; } - case DVL_LOCLIST: - /* Phase 5: emit as DW_FORM_loclistx. Phase 1: empty expr. */ - break; } form_uleb(b, n); buf_write(b, expr, n); @@ -668,7 +636,8 @@ static void emit_subprogram_die(EmitCtx* e, DebugFunc* f) { u8 zero8[8] = {0}; buf_write(&e->info_body, zero8, e->d->c->target.ptr_size); } - add_info_reloc(e, reloc_off, f->sym); + add_addr_reloc(e, &e->info_relocs, &e->ninfo_relocs, &e->info_relocs_cap, + reloc_off, f->sym); fn_size = f->has_pc_range ? (f->end_ofs - f->begin_ofs) : 0; form_u32(&e->info_body, fn_size); { @@ -862,7 +831,8 @@ static void emit_section_line(EmitCtx* e) { u32 buf_ofs = buf_pos(&prog); u8 zeros[8] = {0}; buf_write(&prog, zeros, addr_size); - add_line_reloc(e, buf_ofs, f->sym); + add_addr_reloc(e, &e->line_relocs, &e->nline_relocs, &e->line_relocs_cap, + buf_ofs, f->sym); } for (j = 0; j < f->nrows; ++j) { LineRow* r = &f->rows[j]; @@ -1096,7 +1066,8 @@ static void emit_section_aranges(EmitCtx* e) { u32 reloc_at = buf_pos(&b); u8 zeros[8] = {0}; buf_write(&b, zeros, addr_size); - add_aranges_reloc(e, reloc_at, f->sym); + add_addr_reloc(e, &e->aranges_relocs, &e->naranges_relocs, + &e->aranges_relocs_cap, reloc_at, f->sym); } { u32 fn_size = f->end_ofs - f->begin_ofs; @@ -1156,7 +1127,8 @@ static void emit_section_rnglists(EmitCtx* e) { u32 reloc_at = buf_pos(&b); u8 zeros[8] = {0}; buf_write(&b, zeros, addr_size); - add_rng_reloc(e, reloc_at, f->sym); + add_addr_reloc(e, &e->rng_relocs, &e->nrng_relocs, &e->nrng_relocs_cap, + reloc_at, f->sym); } form_uleb(&b, f->end_ofs - f->begin_ofs); } diff --git a/src/debug/debug_internal.h b/src/debug/debug_internal.h @@ -42,12 +42,11 @@ typedef struct DebugEnumVal { } DebugEnumVal; typedef struct DebugType { - u8 kind; /* DebugTypeKind */ - u8 is_union; /* DTK_RECORD only */ - u8 variadic; /* DTK_FUNC only */ - u8 sibling_visited; /* internal: layout pass */ - u8 base_encoding; /* DebugBaseEncoding (only for DTK_BASE) */ - u8 pad[3]; + u8 kind; /* DebugTypeKind */ + u8 is_union; /* DTK_RECORD only */ + u8 variadic; /* DTK_FUNC only */ + u8 base_encoding; /* DebugBaseEncoding (only for DTK_BASE) */ + u8 pad[4]; Sym name; /* base / typedef / record / enum tag */ u32 byte_size; /* base / record */ u32 align; /* record */ @@ -167,20 +166,6 @@ HASHMAP_DEFINE(SymToU32, Sym, u32, hash_u32); HASHMAP_DEFINE(U32ToU32, u32, u32, hash_u32); HASHMAP_DEFINE(PtrToU32, u64, u32, hash_u64); -/* Loclist entry (Phase 5 placeholder; we register the storage but do not - * yet emit .debug_loclists). */ -typedef struct DebugLocListEntry { - u32 begin_pc; - u32 end_pc; - DebugVarLoc loc; -} DebugLocListEntry; - -typedef struct DebugLocList { - DebugLocListEntry* entries; - u32 nentries; - u32 cap; -} DebugLocList; - /* Abbrev pool — see debug_abbrev.c for encoding. */ typedef struct DebugAbbrevAttr { u16 attr; @@ -233,11 +218,6 @@ struct Debug { /* Line rows pending: latest set_loc */ SrcLoc pending_loc; - /* Loclists */ - DebugLocList* loclists; - u32 nloclists; - u32 loclists_cap; - /* Pre-built type ids for void/builtin reuse — c_debug uses these. */ DebugTypeId void_type; }; @@ -262,7 +242,4 @@ u32 abbrev_intern(DebugAbbrevPool*, Heap*, u16 tag, u8 has_children, /* Encode the entire pool to bytes in `buf`. */ void abbrev_encode(const DebugAbbrevPool*, Buf*); -/* Internal helpers exposed for debug_emit.c */ -const char* debug_remap_path(Debug*, Sym original, size_t* len_out); - #endif diff --git a/src/debug/dwarf_die.c b/src/debug/dwarf_die.c @@ -423,9 +423,12 @@ void dw_build_globals(KitDebugInfo* d) { } } -/* Public accessor for the type module: read attrs given die. */ -void dw_die_pack(KitDebugInfo* d, const DwCu* cu, DwDie* die, DieAttrPack* p) { - u32 off = die->attrs_off; +/* Public accessor for the type module: decode a DIE's attrs into `p` and + * advance *off past them (so the caller lands on the first child DIE). The + * single read_pack pass both decodes and advances — callers no longer need a + * second dw_skip_form loop to step over the attribute stream. */ +void dw_die_pack(KitDebugInfo* d, const DwCu* cu, DwDie* die, DieAttrPack* p, + u32* off) { pack_init(p); - read_pack(d, cu, die, p, &off); + read_pack(d, cu, die, p, off); } diff --git a/src/debug/dwarf_internal.h b/src/debug/dwarf_internal.h @@ -208,10 +208,6 @@ typedef struct DwSubprog { /* ---- The main consumer state ---- */ -typedef struct DwString { - Sym sym; /* interned in compiler->global pool */ -} DwString; - struct KitDebugInfo { const KitContext* ctx; Heap* h; @@ -408,7 +404,8 @@ typedef struct DieAttrPack { u8 inlined; } DieAttrPack; -void dw_die_pack(KitDebugInfo* d, const DwCu* cu, DwDie* die, DieAttrPack* p); +void dw_die_pack(KitDebugInfo* d, const DwCu* cu, DwDie* die, DieAttrPack* p, + u32* off); /* Subprograms */ void dw_build_subs(KitDebugInfo* d); diff --git a/src/debug/dwarf_type.c b/src/debug/dwarf_type.c @@ -71,15 +71,7 @@ static void walk_struct_fields(KitDebugInfo* d, DwCu* cu, u32* off, if (!dw_read_die(d, cu, off, &die)) break; if (die.abbrev->tag == DW_TAG_member) { DieAttrPack p; - dw_die_pack(d, cu, &die, &p); - /* skip past die's attrs */ - { - u32 i; - for (i = 0; i < die.abbrev->nattrs; ++i) { - DwAbbrevAttr* aa = &die.abbrev->attrs[i]; - dw_skip_form(d, cu, aa->form, aa->implicit_const, off); - } - } + dw_die_pack(d, cu, &die, &p, off); /* decodes attrs and advances off */ if (nfields == cap) { u32 ncap = cap ? cap * 2 : 4; DwField* na = @@ -122,14 +114,7 @@ static void walk_enum_values(KitDebugInfo* d, DwCu* cu, u32* off, if (!dw_read_die(d, cu, off, &die)) break; if (die.abbrev->tag == DW_TAG_enumerator) { DieAttrPack p; - dw_die_pack(d, cu, &die, &p); - { - u32 i; - for (i = 0; i < die.abbrev->nattrs; ++i) { - DwAbbrevAttr* aa = &die.abbrev->attrs[i]; - dw_skip_form(d, cu, aa->form, aa->implicit_const, off); - } - } + dw_die_pack(d, cu, &die, &p, off); /* decodes attrs and advances off */ if (nev == cap) { u32 ncap = cap ? cap * 2 : 4; DwEnumVal* na = @@ -166,14 +151,7 @@ static void walk_array_subrange(KitDebugInfo* d, DwCu* cu, u32* off, if (!dw_read_die(d, cu, off, &die)) break; if (die.abbrev->tag == DW_TAG_subrange_type) { DieAttrPack p; - dw_die_pack(d, cu, &die, &p); - { - u32 i; - for (i = 0; i < die.abbrev->nattrs; ++i) { - DwAbbrevAttr* aa = &die.abbrev->attrs[i]; - dw_skip_form(d, cu, aa->form, aa->implicit_const, off); - } - } + dw_die_pack(d, cu, &die, &p, off); /* decodes attrs and advances off */ if (p.has_array_count) t->element_count = p.array_count; if (die.abbrev->has_children) { for (;;) { @@ -208,7 +186,8 @@ KitDwarfType* dw_type_from_die(KitDebugInfo* d, u32 cu_idx, u32 die_offset) { off = die_offset; if (!dw_read_die(d, cu, &off, &die)) return dw_void_type(d); if (!die.abbrev) return dw_void_type(d); - dw_die_pack(d, cu, &die, &p); + /* Decode attrs and advance `off` to the first child DIE. */ + dw_die_pack(d, cu, &die, &p, &off); /* Allocate before recursing — break cycles by interning early. */ t = type_alloc(d); if (!t) return dw_void_type(d); @@ -260,13 +239,8 @@ KitDwarfType* dw_type_from_die(KitDebugInfo* d, u32 cu_idx, u32 die_offset) { p.type_die_offset) : dw_void_type(d); if (die.abbrev->has_children) { + /* `off` already sits at the first child (attrs decoded above). */ u32 cur = off; - /* Skip attrs (already read into p). */ - u32 ii; - for (ii = 0; ii < die.abbrev->nattrs; ++ii) { - DwAbbrevAttr* aa = &die.abbrev->attrs[ii]; - dw_skip_form(d, cu, aa->form, aa->implicit_const, &cur); - } walk_array_subrange(d, cu, &cur, t); } if (t->inner && t->element_count) @@ -279,11 +253,6 @@ KitDwarfType* dw_type_from_die(KitDebugInfo* d, u32 cu_idx, u32 die_offset) { t->byte_size = p.byte_size; if (die.abbrev->has_children) { u32 cur = off; - u32 ii; - for (ii = 0; ii < die.abbrev->nattrs; ++ii) { - DwAbbrevAttr* aa = &die.abbrev->attrs[ii]; - dw_skip_form(d, cu, aa->form, aa->implicit_const, &cur); - } walk_struct_fields(d, cu, &cur, t); } break; @@ -293,11 +262,6 @@ KitDwarfType* dw_type_from_die(KitDebugInfo* d, u32 cu_idx, u32 die_offset) { t->byte_size = p.byte_size; if (die.abbrev->has_children) { u32 cur = off; - u32 ii; - for (ii = 0; ii < die.abbrev->nattrs; ++ii) { - DwAbbrevAttr* aa = &die.abbrev->attrs[ii]; - dw_skip_form(d, cu, aa->form, aa->implicit_const, &cur); - } walk_struct_fields(d, cu, &cur, t); } break; @@ -310,11 +274,6 @@ KitDwarfType* dw_type_from_die(KitDebugInfo* d, u32 cu_idx, u32 die_offset) { : dw_void_type(d); if (die.abbrev->has_children) { u32 cur = off; - u32 ii; - for (ii = 0; ii < die.abbrev->nattrs; ++ii) { - DwAbbrevAttr* aa = &die.abbrev->attrs[ii]; - dw_skip_form(d, cu, aa->form, aa->implicit_const, &cur); - } walk_enum_values(d, cu, &cur, t); } break; diff --git a/src/dist/blob.c b/src/dist/blob.c @@ -17,6 +17,10 @@ static void hash_u64(DistBlake2b* h, uint64_t v) { void dist_blob_id(uint8_t out[DIST_BLAKE2B_LEN], const uint8_t* data, size_t len) { + /* The flat blob id is plain BLAKE2b of the bytes: it doubles as the CAS + * content id and the package format's tree-object hash (pkg_hash), so it + * must stay un-prefixed. (Domain separation from tree ids was considered + * but would require coordinated changes across the whole .kpkg/CAS format.) */ dist_blake2b(out, data, len); } @@ -51,7 +55,7 @@ void dist_blob_empty_root(uint8_t out[DIST_BLAKE2B_LEN]) { int dist_blob_root(uint8_t out[DIST_BLAKE2B_LEN], const uint8_t* data, size_t len, size_t chunk_size) { - uint8_t level[DIST_MAX_FILES][DIST_BLAKE2B_LEN]; + uint8_t level[DIST_BLOB_MAX_CHUNKS][DIST_BLAKE2B_LEN]; size_t leaves, i; if (chunk_size == 0) return DIST_ERR; if (len && !data) return DIST_ERR; @@ -60,7 +64,7 @@ int dist_blob_root(uint8_t out[DIST_BLAKE2B_LEN], const uint8_t* data, return DIST_OK; } leaves = (len + chunk_size - 1u) / chunk_size; - if (leaves > DIST_MAX_FILES) return DIST_ERR; + if (leaves > DIST_BLOB_MAX_CHUNKS) return DIST_ERR; for (i = 0; i < leaves; ++i) { size_t off = i * chunk_size; size_t n = len - off; diff --git a/src/dist/dist.h b/src/dist/dist.h @@ -28,6 +28,11 @@ #define DIST_MAX_FILES 256u #define DIST_MAX_OUTPUTS 16u +/* Maximum number of Merkle leaves (chunks) a single blob may have when its + * root is computed in-memory. Distinct from DIST_MAX_FILES (a package member + * count) even though the two currently share a value. */ +#define DIST_BLOB_MAX_CHUNKS 256u + /* String field caps inside in-memory manifest structs. */ #define DIST_NAME_MAX 128u #define DIST_VERSION_MAX 64u @@ -36,6 +41,7 @@ #define DIST_TRIPLE_MAX 64u #define DIST_KIND_MAX 16u #define DIST_PCONSTRAINT_MAX 64u +#define DIST_URL_MAX 1024u /* URL / URL-template fields (not ustar names) */ /* Result convention: 0 = ok, non-zero = error. */ #define DIST_OK 0 diff --git a/src/dist/dist_parse.h b/src/dist/dist_parse.h @@ -0,0 +1,77 @@ +#ifndef KIT_DIST_DIST_PARSE_H +#define KIT_DIST_DIST_PARSE_H + +#include <kit/core.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "dist.h" + +/* Shared `key = value` text-manifest parse/emit helpers used by the tree, + * package-manifest, and kpkg-descriptor codecs. These were once duplicated + * (with drift) across manifest.c/tree.c/kpkg.c; the single copy here + * reconciles them to the strictest/most-informative variant of each. Provided + * as `static inline` so no extra translation unit (and no Makefile + * registration) is required. */ + +/* Max length of a single emitted "key = value\n" line. */ +#define DIST_KV_LINE_MAX 1024u + +/* Skip leading spaces/tabs, returning a pointer into `s`. */ +static inline char* dist_trim_lead(char* s) { + while (*s == ' ' || *s == '\t') ++s; + return s; +} + +/* Strip trailing spaces/tabs/CR/LF in place. */ +static inline void dist_trim_trail(char* s) { + size_t n = strlen(s); + while (n && (s[n - 1] == ' ' || s[n - 1] == '\t' || s[n - 1] == '\r' || + s[n - 1] == '\n')) + s[--n] = '\0'; +} + +/* Record `msg` into the caller's error buffer (if any) and return DIST_ERR. */ +static inline int dist_set_err(char* err, size_t cap, const char* msg) { + if (err && cap) snprintf(err, cap, "%s", msg); + return DIST_ERR; +} + +/* Copy NUL-terminated `src` into `dst` of capacity `cap`, failing (with an + * error message) if it would not fit. */ +static inline int dist_copy_field(char* dst, size_t cap, const char* src, + char* err, size_t errcap) { + if (strlen(src) >= cap) + return dist_set_err(err, errcap, "field value too long"); + snprintf(dst, cap, "%s", src); + return DIST_OK; +} + +/* Strict base-10 parse of `s` into `*out`: rejects empty input, any non-digit, + * and values that would overflow uint64_t. */ +static inline int dist_parse_u64(const char* s, uint64_t* out) { + uint64_t v = 0; + if (!s || !*s || !out) return DIST_ERR; + for (; *s; ++s) { + unsigned d; + if (*s < '0' || *s > '9') return DIST_ERR; + d = (unsigned)(*s - '0'); + if (v > (UINT64_MAX - (uint64_t)d) / 10u) return DIST_ERR; + v = v * 10u + (uint64_t)d; + } + *out = v; + return DIST_OK; +} + +/* Emit a single "key = value\n" line to `out`. */ +static inline int dist_emit_kv(KitWriter* out, const char* key, + const char* val) { + char line[DIST_KV_LINE_MAX]; + snprintf(line, sizeof line, "%s = %s\n", key, val); + return kit_writer_write(out, line, strlen(line)) == KIT_OK ? DIST_OK + : DIST_ERR; +} + +#endif diff --git a/src/dist/kpkg.c b/src/dist/kpkg.c @@ -5,8 +5,7 @@ #include <string.h> #include "blake2b.h" - -#define DESC_LINE_MAX 1024u +#include "dist_parse.h" static void put_u32le(uint8_t* p, uint32_t v) { p[0] = (uint8_t)v; @@ -36,54 +35,16 @@ static int emit(KitWriter* out, const char* s) { return kit_writer_write(out, s, strlen(s)) == KIT_OK ? DIST_OK : DIST_ERR; } -static int emit_kv(KitWriter* out, const char* key, const char* val) { - char line[DESC_LINE_MAX]; - snprintf(line, sizeof line, "%s = %s\n", key, val); - return emit(out, line); -} - static int emit_u64(KitWriter* out, const char* key, uint64_t v) { char num[24]; snprintf(num, sizeof num, "%llu", (unsigned long long)v); - return emit_kv(out, key, num); + return dist_emit_kv(out, key, num); } static int emit_hex(KitWriter* out, const char* key, const uint8_t* h) { char hex[2 * DIST_BLAKE2B_LEN + 1]; dist_hex_encode(hex, h, DIST_BLAKE2B_LEN); - return emit_kv(out, key, hex); -} - -static char* trim_lead(char* s) { - while (*s == ' ' || *s == '\t') ++s; - return s; -} - -static void trim_trail(char* s) { - size_t n = strlen(s); - while (n && (s[n - 1] == ' ' || s[n - 1] == '\t' || s[n - 1] == '\r' || - s[n - 1] == '\n')) - s[--n] = '\0'; -} - -static int set_err(char* err, size_t cap, const char* msg) { - if (err && cap) snprintf(err, cap, "%s", msg); - return DIST_ERR; -} - -static int parse_u64_strict(const char* s, uint64_t* out) { - uint64_t v = 0; - if (!*s) return DIST_ERR; - while (*s) { - uint64_t digit; - if (*s < '0' || *s > '9') return DIST_ERR; - digit = (uint64_t)(*s - '0'); - if (v > (UINT64_MAX - digit) / 10u) return DIST_ERR; - v = v * 10u + digit; - ++s; - } - *out = v; - return DIST_OK; + return dist_emit_kv(out, key, hex); } static int parse_hex32(uint8_t out[DIST_BLAKE2B_LEN], const char* val) { @@ -91,13 +52,6 @@ static int parse_hex32(uint8_t out[DIST_BLAKE2B_LEN], const char* val) { return dist_hex_decode(out, val, DIST_BLAKE2B_LEN); } -static int copy_field(char* out, size_t cap, const char* val) { - size_t n = strlen(val); - if (n >= cap) return DIST_ERR; - memcpy(out, val, n + 1u); - return DIST_OK; -} - static int seen_once(uint32_t* seen, uint32_t bit) { if (*seen & bit) return DIST_ERR; *seen |= bit; @@ -208,10 +162,12 @@ int dist_kpkg3_descriptor_emit(KitWriter* out, const DistKpkg3Descriptor* d) { size_t i; if (emit(out, "kit-encoding 3\n") != DIST_OK) return DIST_ERR; if (emit_hex(out, "package-id", d->package_id) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "format", "kpkg") != DIST_OK) return DIST_ERR; - if (emit_kv(out, "hash", DIST_KPKG3_HASH) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "tree", DIST_KPKG3_TREE_FORMAT) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "blob", DIST_KPKG3_BLOB_FORMAT) != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "format", "kpkg") != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "hash", DIST_KPKG3_HASH) != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "tree", DIST_KPKG3_TREE_FORMAT) != DIST_OK) + return DIST_ERR; + if (dist_emit_kv(out, "blob", DIST_KPKG3_BLOB_FORMAT) != DIST_OK) + return DIST_ERR; if (emit_u64(out, "chunk-size", d->chunk_size) != DIST_OK) return DIST_ERR; if (emit_u64(out, "alignment", d->alignment) != DIST_OK) return DIST_ERR; if (emit_u64(out, "tree-offset", d->tree_offset) != DIST_OK) return DIST_ERR; @@ -222,7 +178,8 @@ int dist_kpkg3_descriptor_emit(KitWriter* out, const DistKpkg3Descriptor* d) { if (emit_u64(out, "index-size", d->index_size) != DIST_OK) return DIST_ERR; if (emit_u64(out, "index-bytes", d->index_bytes) != DIST_OK) return DIST_ERR; if (emit_hex(out, "index-root", d->index_root) != DIST_OK) return DIST_ERR; - if (d->index_url[0] && emit_kv(out, "index-url", d->index_url) != DIST_OK) + if (d->index_url[0] && + dist_emit_kv(out, "index-url", d->index_url) != DIST_OK) return DIST_ERR; if (emit_u64(out, "content-offset", d->content_offset) != DIST_OK) return DIST_ERR; @@ -241,19 +198,20 @@ int dist_kpkg3_descriptor_emit(KitWriter* out, const DistKpkg3Descriptor* d) { } if (emit_hex(out, "blake2b", d->trees[i].blake2b) != DIST_OK) return DIST_ERR; - if (d->trees[i].url[0] && emit_kv(out, "url", d->trees[i].url) != DIST_OK) + if (d->trees[i].url[0] && + dist_emit_kv(out, "url", d->trees[i].url) != DIST_OK) return DIST_ERR; } for (i = 0; i < d->n_chunk_sources; ++i) { if (emit(out, "\n[chunk-source]\n") != DIST_OK) return DIST_ERR; if (d->chunk_sources[i].kind == DIST_KPKG3_CHUNK_SOURCE_EMBEDDED) { - if (emit_kv(out, "kind", "embedded") != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "kind", "embedded") != DIST_OK) return DIST_ERR; } else if (d->chunk_sources[i].kind == DIST_KPKG3_CHUNK_SOURCE_URL_TEMPLATE) { if (!d->chunk_sources[i].tmpl[0]) return DIST_ERR; - if (emit_kv(out, "kind", "url-template") != DIST_OK) return DIST_ERR; - if (emit_kv(out, "template", d->chunk_sources[i].tmpl) != DIST_OK) + if (dist_emit_kv(out, "kind", "url-template") != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "template", d->chunk_sources[i].tmpl) != DIST_OK) return DIST_ERR; } else { return DIST_ERR; @@ -286,7 +244,16 @@ typedef enum Kpkg3DescriptorSection { #define KPKG3_TOP_CONTENT_SIZE (1u << 15) #define KPKG3_TOP_CONTENT_ROOT (1u << 16) #define KPKG3_TOP_INDEX_URL (1u << 17) -#define KPKG3_TOP_REQUIRED ((1u << 17) - 1u) +/* Explicit OR of every required top-level field (index-url is optional, so it + * is intentionally absent). Listing the bits by name means adding a new + * optional field cannot silently get pulled into the required set. */ +#define KPKG3_TOP_REQUIRED \ + (KPKG3_TOP_PACKAGE_ID | KPKG3_TOP_FORMAT | KPKG3_TOP_HASH | KPKG3_TOP_TREE | \ + KPKG3_TOP_BLOB | KPKG3_TOP_CHUNK_SIZE | KPKG3_TOP_ALIGNMENT | \ + KPKG3_TOP_TREE_OFFSET | KPKG3_TOP_TREE_SIZE | KPKG3_TOP_TREE_ROOT | \ + KPKG3_TOP_INDEX_OFFSET | KPKG3_TOP_INDEX_SIZE | KPKG3_TOP_INDEX_BYTES | \ + KPKG3_TOP_INDEX_ROOT | KPKG3_TOP_CONTENT_OFFSET | KPKG3_TOP_CONTENT_SIZE | \ + KPKG3_TOP_CONTENT_ROOT) #define KPKG3_TREE_SEEN_TREE (1u << 0) #define KPKG3_TREE_SEEN_OFFSET (1u << 1) @@ -306,22 +273,22 @@ static int finish_kpkg3_section(Kpkg3DescriptorSection section, uint32_t seen, int has_size = (seen & KPKG3_TREE_SEEN_SIZE) != 0; if ((seen & KPKG3_TREE_SEEN_TREE) == 0 || (seen & KPKG3_TREE_SEEN_BLAKE2B) == 0) - return set_err(err, errcap, "missing tree-object field"); + return dist_set_err(err, errcap, "missing tree-object field"); if (has_offset != has_size) - return set_err(err, errcap, "partial tree-object embedded range"); + return dist_set_err(err, errcap, "partial tree-object embedded range"); tree->embedded = has_offset; } else if (section == KPKG3_DESC_CHUNK_SOURCE) { DistKpkg3ChunkSource* source = &d->chunk_sources[d->n_chunk_sources - 1u]; if ((seen & KPKG3_CHUNK_SEEN_KIND) == 0) - return set_err(err, errcap, "missing chunk-source kind"); + return dist_set_err(err, errcap, "missing chunk-source kind"); if (source->kind == DIST_KPKG3_CHUNK_SOURCE_URL_TEMPLATE) { if ((seen & KPKG3_CHUNK_SEEN_TEMPLATE) == 0) - return set_err(err, errcap, "missing chunk-source template"); + return dist_set_err(err, errcap, "missing chunk-source template"); } else if (source->kind == DIST_KPKG3_CHUNK_SOURCE_EMBEDDED) { if ((seen & KPKG3_CHUNK_SEEN_TEMPLATE) != 0) - return set_err(err, errcap, "embedded chunk-source has template"); + return dist_set_err(err, errcap, "embedded chunk-source has template"); } else { - return set_err(err, errcap, "bad chunk-source kind"); + return dist_set_err(err, errcap, "bad chunk-source kind"); } } return DIST_OK; @@ -333,77 +300,78 @@ static int parse_kpkg3_top_key(DistKpkg3Descriptor* d, uint32_t* seen, if (strcmp(key, "package-id") == 0) { if (seen_once(seen, KPKG3_TOP_PACKAGE_ID) != DIST_OK || parse_hex32(d->package_id, val) != DIST_OK) - return set_err(err, errcap, "bad package-id"); + return dist_set_err(err, errcap, "bad package-id"); } else if (strcmp(key, "format") == 0) { if (seen_once(seen, KPKG3_TOP_FORMAT) != DIST_OK || strcmp(val, "kpkg") != 0) - return set_err(err, errcap, "bad format"); + return dist_set_err(err, errcap, "bad format"); } else if (strcmp(key, "hash") == 0) { if (seen_once(seen, KPKG3_TOP_HASH) != DIST_OK || strcmp(val, DIST_KPKG3_HASH) != 0) - return set_err(err, errcap, "bad hash algorithm"); + return dist_set_err(err, errcap, "bad hash algorithm"); } else if (strcmp(key, "tree") == 0) { if (seen_once(seen, KPKG3_TOP_TREE) != DIST_OK || strcmp(val, DIST_KPKG3_TREE_FORMAT) != 0) - return set_err(err, errcap, "bad tree format"); + return dist_set_err(err, errcap, "bad tree format"); } else if (strcmp(key, "blob") == 0) { if (seen_once(seen, KPKG3_TOP_BLOB) != DIST_OK || strcmp(val, DIST_KPKG3_BLOB_FORMAT) != 0) - return set_err(err, errcap, "bad blob format"); + return dist_set_err(err, errcap, "bad blob format"); } else if (strcmp(key, "chunk-size") == 0) { if (seen_once(seen, KPKG3_TOP_CHUNK_SIZE) != DIST_OK || - parse_u64_strict(val, &d->chunk_size) != DIST_OK || d->chunk_size == 0) - return set_err(err, errcap, "bad chunk-size"); + dist_parse_u64(val, &d->chunk_size) != DIST_OK || d->chunk_size == 0) + return dist_set_err(err, errcap, "bad chunk-size"); } else if (strcmp(key, "alignment") == 0) { if (seen_once(seen, KPKG3_TOP_ALIGNMENT) != DIST_OK || - parse_u64_strict(val, &d->alignment) != DIST_OK || d->alignment == 0) - return set_err(err, errcap, "bad alignment"); + dist_parse_u64(val, &d->alignment) != DIST_OK || d->alignment == 0) + return dist_set_err(err, errcap, "bad alignment"); } else if (strcmp(key, "tree-offset") == 0) { if (seen_once(seen, KPKG3_TOP_TREE_OFFSET) != DIST_OK || - parse_u64_strict(val, &d->tree_offset) != DIST_OK) - return set_err(err, errcap, "bad tree-offset"); + dist_parse_u64(val, &d->tree_offset) != DIST_OK) + return dist_set_err(err, errcap, "bad tree-offset"); } else if (strcmp(key, "tree-size") == 0) { if (seen_once(seen, KPKG3_TOP_TREE_SIZE) != DIST_OK || - parse_u64_strict(val, &d->tree_size) != DIST_OK) - return set_err(err, errcap, "bad tree-size"); + dist_parse_u64(val, &d->tree_size) != DIST_OK) + return dist_set_err(err, errcap, "bad tree-size"); } else if (strcmp(key, "tree-root") == 0) { if (seen_once(seen, KPKG3_TOP_TREE_ROOT) != DIST_OK || parse_hex32(d->tree_root, val) != DIST_OK) - return set_err(err, errcap, "bad tree-root"); + return dist_set_err(err, errcap, "bad tree-root"); } else if (strcmp(key, "index-offset") == 0) { if (seen_once(seen, KPKG3_TOP_INDEX_OFFSET) != DIST_OK || - parse_u64_strict(val, &d->index_offset) != DIST_OK) - return set_err(err, errcap, "bad index-offset"); + dist_parse_u64(val, &d->index_offset) != DIST_OK) + return dist_set_err(err, errcap, "bad index-offset"); } else if (strcmp(key, "index-size") == 0) { if (seen_once(seen, KPKG3_TOP_INDEX_SIZE) != DIST_OK || - parse_u64_strict(val, &d->index_size) != DIST_OK) - return set_err(err, errcap, "bad index-size"); + dist_parse_u64(val, &d->index_size) != DIST_OK) + return dist_set_err(err, errcap, "bad index-size"); } else if (strcmp(key, "index-bytes") == 0) { if (seen_once(seen, KPKG3_TOP_INDEX_BYTES) != DIST_OK || - parse_u64_strict(val, &d->index_bytes) != DIST_OK) - return set_err(err, errcap, "bad index-bytes"); + dist_parse_u64(val, &d->index_bytes) != DIST_OK) + return dist_set_err(err, errcap, "bad index-bytes"); } else if (strcmp(key, "index-root") == 0) { if (seen_once(seen, KPKG3_TOP_INDEX_ROOT) != DIST_OK || parse_hex32(d->index_root, val) != DIST_OK) - return set_err(err, errcap, "bad index-root"); + return dist_set_err(err, errcap, "bad index-root"); } else if (strcmp(key, "index-url") == 0) { if (seen_once(seen, KPKG3_TOP_INDEX_URL) != DIST_OK || - copy_field(d->index_url, sizeof d->index_url, val) != DIST_OK) - return set_err(err, errcap, "bad index-url"); + dist_copy_field(d->index_url, sizeof d->index_url, val, err, errcap) != + DIST_OK) + return dist_set_err(err, errcap, "bad index-url"); } else if (strcmp(key, "content-offset") == 0) { if (seen_once(seen, KPKG3_TOP_CONTENT_OFFSET) != DIST_OK || - parse_u64_strict(val, &d->content_offset) != DIST_OK) - return set_err(err, errcap, "bad content-offset"); + dist_parse_u64(val, &d->content_offset) != DIST_OK) + return dist_set_err(err, errcap, "bad content-offset"); } else if (strcmp(key, "content-size") == 0) { if (seen_once(seen, KPKG3_TOP_CONTENT_SIZE) != DIST_OK || - parse_u64_strict(val, &d->content_size) != DIST_OK) - return set_err(err, errcap, "bad content-size"); + dist_parse_u64(val, &d->content_size) != DIST_OK) + return dist_set_err(err, errcap, "bad content-size"); } else if (strcmp(key, "content-root") == 0) { if (seen_once(seen, KPKG3_TOP_CONTENT_ROOT) != DIST_OK || parse_hex32(d->content_root, val) != DIST_OK) - return set_err(err, errcap, "bad content-root"); + return dist_set_err(err, errcap, "bad content-root"); } else { - return set_err(err, errcap, "unknown encoding descriptor key"); + return dist_set_err(err, errcap, "unknown encoding descriptor key"); } return DIST_OK; } @@ -414,25 +382,26 @@ static int parse_kpkg3_tree_key(DistKpkg3TreeObject* tree, uint32_t* seen, if (strcmp(key, "tree") == 0) { if (seen_once(seen, KPKG3_TREE_SEEN_TREE) != DIST_OK || parse_hex32(tree->tree, val) != DIST_OK) - return set_err(err, errcap, "bad tree-object tree"); + return dist_set_err(err, errcap, "bad tree-object tree"); } else if (strcmp(key, "offset") == 0) { if (seen_once(seen, KPKG3_TREE_SEEN_OFFSET) != DIST_OK || - parse_u64_strict(val, &tree->offset) != DIST_OK) - return set_err(err, errcap, "bad tree-object offset"); + dist_parse_u64(val, &tree->offset) != DIST_OK) + return dist_set_err(err, errcap, "bad tree-object offset"); } else if (strcmp(key, "size") == 0) { if (seen_once(seen, KPKG3_TREE_SEEN_SIZE) != DIST_OK || - parse_u64_strict(val, &tree->size) != DIST_OK) - return set_err(err, errcap, "bad tree-object size"); + dist_parse_u64(val, &tree->size) != DIST_OK) + return dist_set_err(err, errcap, "bad tree-object size"); } else if (strcmp(key, "blake2b") == 0) { if (seen_once(seen, KPKG3_TREE_SEEN_BLAKE2B) != DIST_OK || parse_hex32(tree->blake2b, val) != DIST_OK) - return set_err(err, errcap, "bad tree-object blake2b"); + return dist_set_err(err, errcap, "bad tree-object blake2b"); } else if (strcmp(key, "url") == 0) { if (seen_once(seen, KPKG3_TREE_SEEN_URL) != DIST_OK || - copy_field(tree->url, sizeof tree->url, val) != DIST_OK) - return set_err(err, errcap, "bad tree-object url"); + dist_copy_field(tree->url, sizeof tree->url, val, err, errcap) != + DIST_OK) + return dist_set_err(err, errcap, "bad tree-object url"); } else { - return set_err(err, errcap, "unknown tree-object key"); + return dist_set_err(err, errcap, "unknown tree-object key"); } return DIST_OK; } @@ -442,20 +411,21 @@ static int parse_kpkg3_chunk_key(DistKpkg3ChunkSource* source, uint32_t* seen, size_t errcap) { if (strcmp(key, "kind") == 0) { if (seen_once(seen, KPKG3_CHUNK_SEEN_KIND) != DIST_OK) - return set_err(err, errcap, "bad chunk-source kind"); + return dist_set_err(err, errcap, "bad chunk-source kind"); if (strcmp(val, "embedded") == 0) { source->kind = DIST_KPKG3_CHUNK_SOURCE_EMBEDDED; } else if (strcmp(val, "url-template") == 0) { source->kind = DIST_KPKG3_CHUNK_SOURCE_URL_TEMPLATE; } else { - return set_err(err, errcap, "bad chunk-source kind"); + return dist_set_err(err, errcap, "bad chunk-source kind"); } } else if (strcmp(key, "template") == 0) { if (seen_once(seen, KPKG3_CHUNK_SEEN_TEMPLATE) != DIST_OK || - copy_field(source->tmpl, sizeof source->tmpl, val) != DIST_OK) - return set_err(err, errcap, "bad chunk-source template"); + dist_copy_field(source->tmpl, sizeof source->tmpl, val, err, errcap) != + DIST_OK) + return dist_set_err(err, errcap, "bad chunk-source template"); } else { - return set_err(err, errcap, "unknown chunk-source key"); + return dist_set_err(err, errcap, "unknown chunk-source key"); } return DIST_OK; } @@ -469,22 +439,23 @@ int dist_kpkg3_descriptor_parse(const uint8_t* data, size_t len, Kpkg3DescriptorSection section = KPKG3_DESC_TOP; memset(d, 0, sizeof *d); while (pos < len) { - char buf[DESC_LINE_MAX], *t, *eq, *key, *val; + char buf[DIST_KV_LINE_MAX], *t, *eq, *key, *val; size_t end = pos, n; while (end < len && data[end] != '\n') ++end; n = end - pos; - if (n >= sizeof buf) return set_err(err, errcap, "line too long"); + if (n >= sizeof buf) return dist_set_err(err, errcap, "line too long"); memcpy(buf, data + pos, n); buf[n] = '\0'; pos = (end < len) ? end + 1u : end; - trim_trail(buf); + dist_trim_trail(buf); if (first) { first = 0; if (strcmp(buf, "kit-encoding 3") != 0) - return set_err(err, errcap, "bad encoding descriptor magic/version"); + return dist_set_err(err, errcap, + "bad encoding descriptor magic/version"); continue; } - t = trim_lead(buf); + t = dist_trim_lead(buf); if (*t == '\0' || *t == '#') continue; if (*t == '[') { if (finish_kpkg3_section(section, section_seen, d, err, errcap) != @@ -493,28 +464,28 @@ int dist_kpkg3_descriptor_parse(const uint8_t* data, size_t len, section_seen = 0; if (strcmp(t, "[tree-object]") == 0) { if (d->n_trees == DIST_MAX_OUTPUTS) - return set_err(err, errcap, "too many tree-object sections"); + return dist_set_err(err, errcap, "too many tree-object sections"); memset(&d->trees[d->n_trees], 0, sizeof d->trees[d->n_trees]); ++d->n_trees; section = KPKG3_DESC_TREE_OBJECT; } else if (strcmp(t, "[chunk-source]") == 0) { if (d->n_chunk_sources == DIST_MAX_OUTPUTS) - return set_err(err, errcap, "too many chunk-source sections"); + return dist_set_err(err, errcap, "too many chunk-source sections"); memset(&d->chunk_sources[d->n_chunk_sources], 0, sizeof d->chunk_sources[d->n_chunk_sources]); ++d->n_chunk_sources; section = KPKG3_DESC_CHUNK_SOURCE; } else { - return set_err(err, errcap, "unknown encoding descriptor section"); + return dist_set_err(err, errcap, "unknown encoding descriptor section"); } continue; } eq = strchr(t, '='); - if (!eq) return set_err(err, errcap, "expected key = value"); + if (!eq) return dist_set_err(err, errcap, "expected key = value"); *eq = '\0'; key = t; - trim_trail(key); - val = trim_lead(eq + 1); + dist_trim_trail(key); + val = dist_trim_lead(eq + 1); if (section == KPKG3_DESC_TOP) { if (parse_kpkg3_top_key(d, &top_seen, key, val, err, errcap) != DIST_OK) return DIST_ERR; @@ -529,13 +500,14 @@ int dist_kpkg3_descriptor_parse(const uint8_t* data, size_t len, return DIST_ERR; } } - if (first) return set_err(err, errcap, "empty encoding descriptor"); + if (first) return dist_set_err(err, errcap, "empty encoding descriptor"); if (finish_kpkg3_section(section, section_seen, d, err, errcap) != DIST_OK) return DIST_ERR; if ((top_seen & KPKG3_TOP_REQUIRED) != KPKG3_TOP_REQUIRED) - return set_err(err, errcap, "missing required encoding descriptor field"); + return dist_set_err(err, errcap, + "missing required encoding descriptor field"); if (d->index_size != 0 && d->index_size != d->index_bytes) - return set_err(err, errcap, "embedded index size mismatch"); + return dist_set_err(err, errcap, "embedded index size mismatch"); return DIST_OK; } diff --git a/src/dist/kpkg.h b/src/dist/kpkg.h @@ -50,7 +50,7 @@ typedef struct DistKpkg3TreeObject { uint64_t offset, size; int embedded; uint8_t blake2b[DIST_BLAKE2B_LEN]; - char url[DIST_PATH_MAX + 1]; + char url[DIST_URL_MAX]; } DistKpkg3TreeObject; typedef enum DistKpkg3ChunkSourceKind { @@ -60,7 +60,7 @@ typedef enum DistKpkg3ChunkSourceKind { typedef struct DistKpkg3ChunkSource { uint32_t kind; - char tmpl[DIST_PATH_MAX + 1]; + char tmpl[DIST_URL_MAX]; } DistKpkg3ChunkSource; typedef struct DistKpkg3Descriptor { @@ -70,7 +70,7 @@ typedef struct DistKpkg3Descriptor { uint8_t tree_root[DIST_BLAKE2B_LEN]; uint64_t index_offset, index_size, index_bytes; uint8_t index_root[DIST_BLAKE2B_LEN]; - char index_url[DIST_PATH_MAX + 1]; + char index_url[DIST_URL_MAX]; uint64_t content_offset, content_size; uint8_t content_root[DIST_BLAKE2B_LEN]; DistKpkg3TreeObject trees[DIST_MAX_OUTPUTS]; diff --git a/src/dist/manifest.c b/src/dist/manifest.c @@ -1,109 +1,25 @@ #include "manifest.h" #include <stdio.h> -#include <stdlib.h> #include <string.h> -#define DIST_LINE_MAX 1024u - -#define F_NAME 0x01u -#define F_VERSION 0x02u -#define F_HASH 0x04u -#define F_ID 0x08u -#define F_PATH 0x10u -#define F_KIND 0x20u -#define F_BLAKE2B 0x40u -#define F_ROOT 0x80u -#define F_SIZE 0x100u - -typedef enum { SEC_TOP, SEC_ART, SEC_DEP } Section; +#include "dist_parse.h" static int emit(KitWriter* out, const char* s) { return kit_writer_write(out, s, strlen(s)) == KIT_OK ? DIST_OK : DIST_ERR; } -static int emit_kv(KitWriter* out, const char* key, const char* val) { - char line[DIST_LINE_MAX]; - snprintf(line, sizeof line, "%s = %s\n", key, val); - return emit(out, line); -} - static int emit_hex(KitWriter* out, const char* key, const uint8_t* h, size_t n) { char hex[2 * DIST_BLAKE2B_LEN + 1]; dist_hex_encode(hex, h, n); - return emit_kv(out, key, hex); + return dist_emit_kv(out, key, hex); } static int emit_u64(KitWriter* out, const char* key, uint64_t v) { char num[24]; snprintf(num, sizeof num, "%llu", (unsigned long long)v); - return emit_kv(out, key, num); -} - -int dist_manifest_emit(const DistManifest* m, KitWriter* out) { - size_t i; - if (emit(out, DIST_MANIFEST_MAGIC "\n") != DIST_OK) return DIST_ERR; - if (emit_kv(out, "name", m->name) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "version", m->version) != DIST_OK) return DIST_ERR; - if (m->description[0] && - emit_kv(out, "description", m->description) != DIST_OK) - return DIST_ERR; - if (emit_kv(out, "hash", DIST_MANIFEST_HASH) != DIST_OK) return DIST_ERR; - - for (i = 0; i < m->n_artifacts; ++i) { - const DistArtifact* a = &m->artifacts[i]; - if (emit(out, "\n[artifact]\n") != DIST_OK) return DIST_ERR; - if (emit_u64(out, "id", a->id) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "path", a->path) != DIST_OK) return DIST_ERR; - if (a->target[0] && emit_kv(out, "target", a->target) != DIST_OK) - return DIST_ERR; - if (emit_kv(out, "kind", a->kind) != DIST_OK) return DIST_ERR; - if (emit_u64(out, "size", a->size) != DIST_OK) return DIST_ERR; - if (emit_hex(out, "blake2b", a->blake2b, DIST_BLAKE2B_LEN) != DIST_OK) - return DIST_ERR; - if (emit_hex(out, "root", a->root, DIST_BLAKE2B_LEN) != DIST_OK) - return DIST_ERR; - if (a->entry && emit_kv(out, "entry", "true") != DIST_OK) return DIST_ERR; - } - - for (i = 0; i < m->n_deps; ++i) { - const DistDependency* d = &m->deps[i]; - if (emit(out, "\n[dependency]\n") != DIST_OK) return DIST_ERR; - if (emit_kv(out, "name", d->name) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "version", d->version) != DIST_OK) return DIST_ERR; - if (d->has_blake2b && - emit_hex(out, "blake2b", d->blake2b, DIST_BLAKE2B_LEN) != DIST_OK) - return DIST_ERR; - if (d->has_keyid && - emit_hex(out, "key", d->keyid, DIST_KEYID_LEN) != DIST_OK) - return DIST_ERR; - } - return DIST_OK; -} - -static char* trim_lead(char* s) { - while (*s == ' ' || *s == '\t') ++s; - return s; -} - -static void trim_trail(char* s) { - size_t n = strlen(s); - while (n && (s[n - 1] == ' ' || s[n - 1] == '\t' || s[n - 1] == '\r' || - s[n - 1] == '\n')) - s[--n] = '\0'; -} - -static int set_err(char* err, size_t cap, const char* msg) { - if (err && cap) snprintf(err, cap, "%s", msg); - return DIST_ERR; -} - -static int copy_field(char* dst, size_t cap, const char* src, char* err, - size_t errcap) { - if (strlen(src) >= cap) return set_err(err, errcap, "field value too long"); - snprintf(dst, cap, "%s", src); - return DIST_OK; + return dist_emit_kv(out, key, num); } static int kind_valid(const char* k) { @@ -115,7 +31,7 @@ static int kind_valid(const char* k) { int dist_manifest_path_valid(const char* p) { size_t start = 0, i; - if (!p[0] || p[0] == '/') return 0; + if (!p || !p[0] || p[0] == '/') return 0; for (i = 0;; ++i) { char c = p[i]; if (c == '\\' || c == ':') return 0; @@ -130,192 +46,6 @@ int dist_manifest_path_valid(const char* p) { } } -static int parse_u64(const char* s, uint64_t* out) { - char* end = NULL; - unsigned long long v; - if (!*s) return DIST_ERR; - v = strtoull(s, &end, 10); - if (!end || *end != '\0') return DIST_ERR; - *out = (uint64_t)v; - return DIST_OK; -} - -static int finalize(Section sec, uint32_t seen, char* err, size_t errcap) { - if (sec == SEC_TOP) { - if ((seen & (F_NAME | F_VERSION | F_HASH)) != (F_NAME | F_VERSION | F_HASH)) - return set_err(err, errcap, "missing required top-level field"); - } else if (sec == SEC_ART) { - if ((seen & (F_ID | F_PATH | F_KIND | F_BLAKE2B | F_ROOT | F_SIZE)) != - (F_ID | F_PATH | F_KIND | F_BLAKE2B | F_ROOT | F_SIZE)) - return set_err(err, errcap, "missing required [artifact] field"); - } else { - if ((seen & (F_NAME | F_VERSION)) != (F_NAME | F_VERSION)) - return set_err(err, errcap, "missing required [dependency] field"); - } - return DIST_OK; -} - -int dist_manifest_parse(const uint8_t* data, size_t len, DistManifest* m, - char* err, size_t errcap) { - size_t pos = 0; - int first = 1; - Section sec = SEC_TOP; - uint32_t seen = 0; - DistArtifact* art = NULL; - DistDependency* dep = NULL; - - memset(m, 0, sizeof *m); - - while (pos < len) { - char buf[DIST_LINE_MAX]; - size_t end = pos; - size_t n; - char *t, *key, *val, *eq; - - while (end < len && data[end] != '\n') ++end; - n = end - pos; - if (n >= sizeof buf) return set_err(err, errcap, "line too long"); - memcpy(buf, data + pos, n); - buf[n] = '\0'; - pos = (end < len) ? end + 1 : end; - trim_trail(buf); - - if (first) { - first = 0; - if (strcmp(buf, DIST_MANIFEST_MAGIC) != 0) - return set_err(err, errcap, "bad manifest magic/version"); - continue; - } - - t = trim_lead(buf); - if (*t == '\0' || *t == '#') continue; - - if (*t == '[') { - if (finalize(sec, seen, err, errcap) != DIST_OK) return DIST_ERR; - seen = 0; - if (strcmp(t, "[artifact]") == 0) { - if (m->n_artifacts >= DIST_MAX_ARTIFACTS) - return set_err(err, errcap, "too many artifacts"); - sec = SEC_ART; - art = &m->artifacts[m->n_artifacts++]; - } else if (strcmp(t, "[dependency]") == 0) { - if (m->n_deps >= DIST_MAX_DEPS) - return set_err(err, errcap, "too many dependencies"); - sec = SEC_DEP; - dep = &m->deps[m->n_deps++]; - } else { - return set_err(err, errcap, "unknown section"); - } - continue; - } - - eq = strchr(t, '='); - if (!eq) return set_err(err, errcap, "expected key = value"); - *eq = '\0'; - key = t; - trim_trail(key); - val = trim_lead(eq + 1); - - if (sec == SEC_TOP) { - if (strcmp(key, "name") == 0) { - if (copy_field(m->name, sizeof m->name, val, err, errcap)) - return DIST_ERR; - seen |= F_NAME; - } else if (strcmp(key, "version") == 0) { - if (copy_field(m->version, sizeof m->version, val, err, errcap)) - return DIST_ERR; - seen |= F_VERSION; - } else if (strcmp(key, "description") == 0) { - if (copy_field(m->description, sizeof m->description, val, err, errcap)) - return DIST_ERR; - } else if (strcmp(key, "hash") == 0) { - if (strcmp(val, DIST_MANIFEST_HASH) != 0) - return set_err(err, errcap, "unsupported hash algorithm"); - seen |= F_HASH; - } else { - return set_err(err, errcap, "unknown top-level key"); - } - } else if (sec == SEC_ART) { - if (strcmp(key, "id") == 0) { - if (parse_u64(val, &art->id) != DIST_OK) - return set_err(err, errcap, "bad artifact id"); - seen |= F_ID; - } else if (strcmp(key, "path") == 0) { - if (!dist_manifest_path_valid(val)) - return set_err(err, errcap, "unsafe artifact path"); - if (copy_field(art->path, sizeof art->path, val, err, errcap)) - return DIST_ERR; - seen |= F_PATH; - } else if (strcmp(key, "target") == 0) { - if (copy_field(art->target, sizeof art->target, val, err, errcap)) - return DIST_ERR; - } else if (strcmp(key, "kind") == 0) { - if (!kind_valid(val)) - return set_err(err, errcap, "unknown artifact kind"); - if (copy_field(art->kind, sizeof art->kind, val, err, errcap)) - return DIST_ERR; - seen |= F_KIND; - } else if (strcmp(key, "size") == 0) { - if (parse_u64(val, &art->size) != DIST_OK) - return set_err(err, errcap, "bad artifact size"); - seen |= F_SIZE; - } else if (strcmp(key, "blake2b") == 0) { - if (strlen(val) != 2 * DIST_BLAKE2B_LEN || - dist_hex_decode(art->blake2b, val, DIST_BLAKE2B_LEN) != DIST_OK) - return set_err(err, errcap, "bad artifact blake2b"); - seen |= F_BLAKE2B; - } else if (strcmp(key, "root") == 0) { - if (strlen(val) != 2 * DIST_BLAKE2B_LEN || - dist_hex_decode(art->root, val, DIST_BLAKE2B_LEN) != DIST_OK) - return set_err(err, errcap, "bad artifact root"); - seen |= F_ROOT; - } else if (strcmp(key, "entry") == 0) { - art->entry = (strcmp(val, "true") == 0); - if (!art->entry && strcmp(val, "false") != 0) - return set_err(err, errcap, "bad entry value"); - } else { - return set_err(err, errcap, "unknown [artifact] key"); - } - } else { - if (strcmp(key, "name") == 0) { - if (copy_field(dep->name, sizeof dep->name, val, err, errcap)) - return DIST_ERR; - seen |= F_NAME; - } else if (strcmp(key, "version") == 0) { - if (copy_field(dep->version, sizeof dep->version, val, err, errcap)) - return DIST_ERR; - seen |= F_VERSION; - } else if (strcmp(key, "blake2b") == 0) { - if (strlen(val) != 2 * DIST_BLAKE2B_LEN || - dist_hex_decode(dep->blake2b, val, DIST_BLAKE2B_LEN) != DIST_OK) - return set_err(err, errcap, "bad dependency blake2b"); - dep->has_blake2b = 1; - } else if (strcmp(key, "key") == 0) { - if (strlen(val) != 2 * DIST_KEYID_LEN || - dist_hex_decode(dep->keyid, val, DIST_KEYID_LEN) != DIST_OK) - return set_err(err, errcap, "bad dependency key id"); - dep->has_keyid = 1; - } else { - return set_err(err, errcap, "unknown [dependency] key"); - } - } - } - - if (finalize(sec, seen, err, errcap) != DIST_OK) return DIST_ERR; - { - size_t i, j; - for (i = 0; i < m->n_artifacts; ++i) { - for (j = i + 1u; j < m->n_artifacts; ++j) { - if (m->artifacts[i].id == m->artifacts[j].id) - return set_err(err, errcap, "duplicate artifact id"); - if (strcmp(m->artifacts[i].path, m->artifacts[j].path) == 0) - return set_err(err, errcap, "duplicate artifact path"); - } - } - } - return DIST_OK; -} - #define P3_F_NAME 0x00000001u #define P3_F_VERSION 0x00000002u #define P3_F_DESCRIPTION 0x00000004u @@ -361,25 +91,11 @@ static int parse_bool3(const char* s, int* out) { return DIST_ERR; } -static int parse_u64_dec3(const char* s, uint64_t* out) { - uint64_t v = 0; - if (!*s) return DIST_ERR; - for (; *s; ++s) { - unsigned digit; - if (*s < '0' || *s > '9') return DIST_ERR; - digit = (unsigned)(*s - '0'); - if (v > (UINT64_MAX - (uint64_t)digit) / 10u) return DIST_ERR; - v = v * 10u + (uint64_t)digit; - } - *out = v; - return DIST_OK; -} - static int decode_hash3(uint8_t out[DIST_BLAKE2B_LEN], const char* val, const char* err_msg, char* err, size_t errcap) { if (strlen(val) != 2u * DIST_BLAKE2B_LEN || dist_hex_decode(out, val, DIST_BLAKE2B_LEN) != DIST_OK) - return set_err(err, errcap, err_msg); + return dist_set_err(err, errcap, err_msg); return DIST_OK; } @@ -387,13 +103,13 @@ static int decode_keyid3(uint8_t out[DIST_KEYID_LEN], const char* val, char* err, size_t errcap) { if (strlen(val) != 2u * DIST_KEYID_LEN || dist_hex_decode(out, val, DIST_KEYID_LEN) != DIST_OK) - return set_err(err, errcap, "bad dependency key id"); + return dist_set_err(err, errcap, "bad dependency key id"); return DIST_OK; } static int seen_once3(uint32_t* seen, uint32_t bit, const char* msg, char* err, size_t errcap) { - if (*seen & bit) return set_err(err, errcap, msg); + if (*seen & bit) return dist_set_err(err, errcap, msg); *seen |= bit; return DIST_OK; } @@ -412,18 +128,18 @@ static int finalize_package_section3(PackageSection sec, uint32_t seen, if ((seen & (P3_F_NAME | P3_F_VERSION | P3_F_HASH | P3_F_TREE_FORMAT | P3_F_BLOB_FORMAT)) != (P3_F_NAME | P3_F_VERSION | P3_F_HASH | P3_F_TREE_FORMAT | P3_F_BLOB_FORMAT)) - return set_err(err, errcap, "missing required top-level field"); + return dist_set_err(err, errcap, "missing required top-level field"); } else if (sec == P3_SEC_OUTPUT) { if ((seen & (P3_F_ID | P3_F_OUTPUT_NAME | P3_F_TREE_ID)) != (P3_F_ID | P3_F_OUTPUT_NAME | P3_F_TREE_ID)) - return set_err(err, errcap, "missing required [output] field"); + return dist_set_err(err, errcap, "missing required [output] field"); } else if (sec == P3_SEC_ARTIFACT) { if ((seen & (P3_F_OUTPUT | P3_F_PATH | P3_F_KIND)) != (P3_F_OUTPUT | P3_F_PATH | P3_F_KIND)) - return set_err(err, errcap, "missing required [artifact] field"); + return dist_set_err(err, errcap, "missing required [artifact] field"); } else { if ((seen & (P3_F_NAME | P3_F_VERSION)) != (P3_F_NAME | P3_F_VERSION)) - return set_err(err, errcap, "missing required [dependency] field"); + return dist_set_err(err, errcap, "missing required [dependency] field"); } return DIST_OK; } @@ -435,48 +151,48 @@ int dist_package_manifest_validate(const DistPackageManifest* m, char* err, if (!field_text_valid(m->name, 1) || !field_text_valid(m->version, 1) || !field_text_valid(m->description, 0)) - return set_err(err, errcap, "bad package string field"); - if (m->n_outputs == 0) return set_err(err, errcap, "missing [output]"); + return dist_set_err(err, errcap, "bad package string field"); + if (m->n_outputs == 0) return dist_set_err(err, errcap, "missing [output]"); if (m->n_outputs > DIST_MAX_OUTPUTS) - return set_err(err, errcap, "too many outputs"); + return dist_set_err(err, errcap, "too many outputs"); if (m->n_artifacts > DIST_MAX_ARTIFACTS) - return set_err(err, errcap, "too many artifacts"); + return dist_set_err(err, errcap, "too many artifacts"); if (m->n_deps > DIST_MAX_DEPS) - return set_err(err, errcap, "too many dependencies"); + return dist_set_err(err, errcap, "too many dependencies"); for (i = 0; i < m->n_outputs; ++i) { const DistPackageOutput* out = &m->outputs[i]; if (!field_text_valid(out->name, 1) || !field_text_valid(out->target, 0)) - return set_err(err, errcap, "bad output string field"); + return dist_set_err(err, errcap, "bad output string field"); if (out->is_default) ++default_outputs; for (j = i + 1u; j < m->n_outputs; ++j) { if (out->id == m->outputs[j].id) - return set_err(err, errcap, "duplicate output id"); + return dist_set_err(err, errcap, "duplicate output id"); } } if (default_outputs > 1u) - return set_err(err, errcap, "duplicate default output"); + return dist_set_err(err, errcap, "duplicate default output"); for (i = 0; i < m->n_artifacts; ++i) { const DistPackageArtifact* art = &m->artifacts[i]; if (find_output3(m, art->output_id) < 0) - return set_err(err, errcap, "artifact references unknown output"); + return dist_set_err(err, errcap, "artifact references unknown output"); if (!field_text_valid(art->path, 1) || !dist_manifest_path_valid(art->path)) - return set_err(err, errcap, "unsafe artifact path"); + return dist_set_err(err, errcap, "unsafe artifact path"); if (!kind_valid(art->kind)) - return set_err(err, errcap, "unknown artifact kind"); + return dist_set_err(err, errcap, "unknown artifact kind"); for (j = i + 1u; j < m->n_artifacts; ++j) { const DistPackageArtifact* other = &m->artifacts[j]; if (art->output_id == other->output_id && strcmp(art->path, other->path) == 0) - return set_err(err, errcap, "duplicate artifact path"); + return dist_set_err(err, errcap, "duplicate artifact path"); } } for (i = 0; i < m->n_deps; ++i) { const DistPackageDependency* dep = &m->deps[i]; if (!field_text_valid(dep->name, 1) || !field_text_valid(dep->version, 1)) - return set_err(err, errcap, "bad dependency string field"); + return dist_set_err(err, errcap, "bad dependency string field"); } return DIST_OK; @@ -490,28 +206,28 @@ int dist_package_manifest_emit(const DistPackageManifest* m, KitWriter* out) { return DIST_ERR; if (emit(out, DIST_PACKAGE3_MAGIC "\n") != DIST_OK) return DIST_ERR; - if (emit_kv(out, "name", m->name) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "version", m->version) != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "name", m->name) != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "version", m->version) != DIST_OK) return DIST_ERR; if (m->description[0] && - emit_kv(out, "description", m->description) != DIST_OK) + dist_emit_kv(out, "description", m->description) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "hash", DIST_PACKAGE3_HASH) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "tree", DIST_PACKAGE3_TREE_FORMAT) != DIST_OK) + if (dist_emit_kv(out, "hash", DIST_PACKAGE3_HASH) != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "tree", DIST_PACKAGE3_TREE_FORMAT) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "blob", DIST_PACKAGE3_BLOB_FORMAT) != DIST_OK) + if (dist_emit_kv(out, "blob", DIST_PACKAGE3_BLOB_FORMAT) != DIST_OK) return DIST_ERR; for (i = 0; i < m->n_outputs; ++i) { const DistPackageOutput* pkg_out = &m->outputs[i]; if (emit(out, "\n[output]\n") != DIST_OK) return DIST_ERR; if (emit_u64(out, "id", pkg_out->id) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "name", pkg_out->name) != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "name", pkg_out->name) != DIST_OK) return DIST_ERR; if (emit_hex(out, "tree", pkg_out->tree, DIST_BLAKE2B_LEN) != DIST_OK) return DIST_ERR; if (pkg_out->target[0] && - emit_kv(out, "target", pkg_out->target) != DIST_OK) + dist_emit_kv(out, "target", pkg_out->target) != DIST_OK) return DIST_ERR; - if (pkg_out->is_default && emit_kv(out, "default", "true") != DIST_OK) + if (pkg_out->is_default && dist_emit_kv(out, "default", "true") != DIST_OK) return DIST_ERR; } @@ -519,16 +235,17 @@ int dist_package_manifest_emit(const DistPackageManifest* m, KitWriter* out) { const DistPackageArtifact* art = &m->artifacts[i]; if (emit(out, "\n[artifact]\n") != DIST_OK) return DIST_ERR; if (emit_u64(out, "output", art->output_id) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "path", art->path) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "kind", art->kind) != DIST_OK) return DIST_ERR; - if (art->entry && emit_kv(out, "entry", "true") != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "path", art->path) != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "kind", art->kind) != DIST_OK) return DIST_ERR; + if (art->entry && dist_emit_kv(out, "entry", "true") != DIST_OK) + return DIST_ERR; } for (i = 0; i < m->n_deps; ++i) { const DistPackageDependency* dep = &m->deps[i]; if (emit(out, "\n[dependency]\n") != DIST_OK) return DIST_ERR; - if (emit_kv(out, "name", dep->name) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "version", dep->version) != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "name", dep->name) != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "version", dep->version) != DIST_OK) return DIST_ERR; if (dep->has_package && emit_hex(out, "package", dep->package, DIST_BLAKE2B_LEN) != DIST_OK) return DIST_ERR; @@ -554,27 +271,27 @@ int dist_package_manifest_parse(const uint8_t* data, size_t len, memset(m, 0, sizeof *m); while (pos < len) { - char buf[DIST_LINE_MAX]; + char buf[DIST_KV_LINE_MAX]; size_t end = pos; size_t n; char *t, *key, *val, *eq; while (end < len && data[end] != '\n') ++end; n = end - pos; - if (n >= sizeof buf) return set_err(err, errcap, "line too long"); + if (n >= sizeof buf) return dist_set_err(err, errcap, "line too long"); memcpy(buf, data + pos, n); buf[n] = '\0'; pos = (end < len) ? end + 1 : end; - trim_trail(buf); + dist_trim_trail(buf); if (first) { first = 0; if (strcmp(buf, DIST_PACKAGE3_MAGIC) != 0) - return set_err(err, errcap, "bad package manifest magic/version"); + return dist_set_err(err, errcap, "bad package manifest magic/version"); continue; } - t = trim_lead(buf); + t = dist_trim_lead(buf); if (*t == '\0' || *t == '#') continue; if (*t == '[') { @@ -583,31 +300,31 @@ int dist_package_manifest_parse(const uint8_t* data, size_t len, seen = 0; if (strcmp(t, "[output]") == 0) { if (m->n_outputs >= DIST_MAX_OUTPUTS) - return set_err(err, errcap, "too many outputs"); + return dist_set_err(err, errcap, "too many outputs"); sec = P3_SEC_OUTPUT; pkg_out = &m->outputs[m->n_outputs++]; } else if (strcmp(t, "[artifact]") == 0) { if (m->n_artifacts >= DIST_MAX_ARTIFACTS) - return set_err(err, errcap, "too many artifacts"); + return dist_set_err(err, errcap, "too many artifacts"); sec = P3_SEC_ARTIFACT; art = &m->artifacts[m->n_artifacts++]; } else if (strcmp(t, "[dependency]") == 0) { if (m->n_deps >= DIST_MAX_DEPS) - return set_err(err, errcap, "too many dependencies"); + return dist_set_err(err, errcap, "too many dependencies"); sec = P3_SEC_DEPENDENCY; dep = &m->deps[m->n_deps++]; } else { - return set_err(err, errcap, "unknown section"); + return dist_set_err(err, errcap, "unknown section"); } continue; } eq = strchr(t, '='); - if (!eq) return set_err(err, errcap, "expected key = value"); + if (!eq) return dist_set_err(err, errcap, "expected key = value"); *eq = '\0'; key = t; - trim_trail(key); - val = trim_lead(eq + 1); + dist_trim_trail(key); + val = dist_trim_lead(eq + 1); if (sec == P3_SEC_TOP) { if (strcmp(key, "name") == 0) { @@ -615,60 +332,62 @@ int dist_package_manifest_parse(const uint8_t* data, size_t len, errcap) != DIST_OK) return DIST_ERR; if (!field_text_valid(val, 1)) - return set_err(err, errcap, "bad package name"); - if (copy_field(m->name, sizeof m->name, val, err, errcap)) + return dist_set_err(err, errcap, "bad package name"); + if (dist_copy_field(m->name, sizeof m->name, val, err, errcap)) return DIST_ERR; } else if (strcmp(key, "version") == 0) { if (seen_once3(&seen, P3_F_VERSION, "duplicate top-level key", err, errcap) != DIST_OK) return DIST_ERR; if (!field_text_valid(val, 1)) - return set_err(err, errcap, "bad package version"); - if (copy_field(m->version, sizeof m->version, val, err, errcap)) + return dist_set_err(err, errcap, "bad package version"); + if (dist_copy_field(m->version, sizeof m->version, val, err, errcap)) return DIST_ERR; } else if (strcmp(key, "description") == 0) { if (seen_once3(&seen, P3_F_DESCRIPTION, "duplicate top-level key", err, errcap) != DIST_OK) return DIST_ERR; if (!field_text_valid(val, 0)) - return set_err(err, errcap, "bad package description"); - if (copy_field(m->description, sizeof m->description, val, err, errcap)) + return dist_set_err(err, errcap, "bad package description"); + if (dist_copy_field(m->description, sizeof m->description, val, err, + errcap)) return DIST_ERR; } else if (strcmp(key, "hash") == 0) { if (seen_once3(&seen, P3_F_HASH, "duplicate top-level key", err, errcap) != DIST_OK) return DIST_ERR; if (strcmp(val, DIST_PACKAGE3_HASH) != 0) - return set_err(err, errcap, "unsupported hash algorithm"); + return dist_set_err(err, errcap, "unsupported hash algorithm"); } else if (strcmp(key, "tree") == 0) { if (seen_once3(&seen, P3_F_TREE_FORMAT, "duplicate top-level key", err, errcap) != DIST_OK) return DIST_ERR; if (strcmp(val, DIST_PACKAGE3_TREE_FORMAT) != 0) - return set_err(err, errcap, "unsupported tree format"); + return dist_set_err(err, errcap, "unsupported tree format"); } else if (strcmp(key, "blob") == 0) { if (seen_once3(&seen, P3_F_BLOB_FORMAT, "duplicate top-level key", err, errcap) != DIST_OK) return DIST_ERR; if (strcmp(val, DIST_PACKAGE3_BLOB_FORMAT) != 0) - return set_err(err, errcap, "unsupported blob format"); + return dist_set_err(err, errcap, "unsupported blob format"); } else { - return set_err(err, errcap, "unknown top-level key"); + return dist_set_err(err, errcap, "unknown top-level key"); } } else if (sec == P3_SEC_OUTPUT) { if (strcmp(key, "id") == 0) { if (seen_once3(&seen, P3_F_ID, "duplicate [output] key", err, errcap) != DIST_OK) return DIST_ERR; - if (parse_u64_dec3(val, &pkg_out->id) != DIST_OK) - return set_err(err, errcap, "bad output id"); + if (dist_parse_u64(val, &pkg_out->id) != DIST_OK) + return dist_set_err(err, errcap, "bad output id"); } else if (strcmp(key, "name") == 0) { if (seen_once3(&seen, P3_F_OUTPUT_NAME, "duplicate [output] key", err, errcap) != DIST_OK) return DIST_ERR; if (!field_text_valid(val, 1)) - return set_err(err, errcap, "bad output name"); - if (copy_field(pkg_out->name, sizeof pkg_out->name, val, err, errcap)) + return dist_set_err(err, errcap, "bad output name"); + if (dist_copy_field(pkg_out->name, sizeof pkg_out->name, val, err, + errcap)) return DIST_ERR; } else if (strcmp(key, "tree") == 0) { if (seen_once3(&seen, P3_F_TREE_ID, "duplicate [output] key", err, @@ -682,50 +401,50 @@ int dist_package_manifest_parse(const uint8_t* data, size_t len, errcap) != DIST_OK) return DIST_ERR; if (!field_text_valid(val, 0)) - return set_err(err, errcap, "bad output target"); - if (copy_field(pkg_out->target, sizeof pkg_out->target, val, err, - errcap)) + return dist_set_err(err, errcap, "bad output target"); + if (dist_copy_field(pkg_out->target, sizeof pkg_out->target, val, err, + errcap)) return DIST_ERR; } else if (strcmp(key, "default") == 0) { if (seen_once3(&seen, P3_F_DEFAULT, "duplicate [output] key", err, errcap) != DIST_OK) return DIST_ERR; if (parse_bool3(val, &pkg_out->is_default) != DIST_OK) - return set_err(err, errcap, "bad default value"); + return dist_set_err(err, errcap, "bad default value"); } else { - return set_err(err, errcap, "unknown [output] key"); + return dist_set_err(err, errcap, "unknown [output] key"); } } else if (sec == P3_SEC_ARTIFACT) { if (strcmp(key, "output") == 0) { if (seen_once3(&seen, P3_F_OUTPUT, "duplicate [artifact] key", err, errcap) != DIST_OK) return DIST_ERR; - if (parse_u64_dec3(val, &art->output_id) != DIST_OK) - return set_err(err, errcap, "bad artifact output id"); + if (dist_parse_u64(val, &art->output_id) != DIST_OK) + return dist_set_err(err, errcap, "bad artifact output id"); } else if (strcmp(key, "path") == 0) { if (seen_once3(&seen, P3_F_PATH, "duplicate [artifact] key", err, errcap) != DIST_OK) return DIST_ERR; if (!dist_manifest_path_valid(val)) - return set_err(err, errcap, "unsafe artifact path"); - if (copy_field(art->path, sizeof art->path, val, err, errcap)) + return dist_set_err(err, errcap, "unsafe artifact path"); + if (dist_copy_field(art->path, sizeof art->path, val, err, errcap)) return DIST_ERR; } else if (strcmp(key, "kind") == 0) { if (seen_once3(&seen, P3_F_KIND, "duplicate [artifact] key", err, errcap) != DIST_OK) return DIST_ERR; if (!kind_valid(val)) - return set_err(err, errcap, "unknown artifact kind"); - if (copy_field(art->kind, sizeof art->kind, val, err, errcap)) + return dist_set_err(err, errcap, "unknown artifact kind"); + if (dist_copy_field(art->kind, sizeof art->kind, val, err, errcap)) return DIST_ERR; } else if (strcmp(key, "entry") == 0) { if (seen_once3(&seen, P3_F_ENTRY, "duplicate [artifact] key", err, errcap) != DIST_OK) return DIST_ERR; if (parse_bool3(val, &art->entry) != DIST_OK) - return set_err(err, errcap, "bad entry value"); + return dist_set_err(err, errcap, "bad entry value"); } else { - return set_err(err, errcap, "unknown [artifact] key"); + return dist_set_err(err, errcap, "unknown [artifact] key"); } } else { if (strcmp(key, "name") == 0) { @@ -733,16 +452,17 @@ int dist_package_manifest_parse(const uint8_t* data, size_t len, errcap) != DIST_OK) return DIST_ERR; if (!field_text_valid(val, 1)) - return set_err(err, errcap, "bad dependency name"); - if (copy_field(dep->name, sizeof dep->name, val, err, errcap)) + return dist_set_err(err, errcap, "bad dependency name"); + if (dist_copy_field(dep->name, sizeof dep->name, val, err, errcap)) return DIST_ERR; } else if (strcmp(key, "version") == 0) { if (seen_once3(&seen, P3_F_VERSION, "duplicate [dependency] key", err, errcap) != DIST_OK) return DIST_ERR; if (!field_text_valid(val, 1)) - return set_err(err, errcap, "bad dependency version"); - if (copy_field(dep->version, sizeof dep->version, val, err, errcap)) + return dist_set_err(err, errcap, "bad dependency version"); + if (dist_copy_field(dep->version, sizeof dep->version, val, err, + errcap)) return DIST_ERR; } else if (strcmp(key, "package") == 0) { if (seen_once3(&seen, P3_F_PACKAGE, "duplicate [dependency] key", err, @@ -760,12 +480,13 @@ int dist_package_manifest_parse(const uint8_t* data, size_t len, return DIST_ERR; dep->has_keyid = 1; } else { - return set_err(err, errcap, "unknown [dependency] key"); + return dist_set_err(err, errcap, "unknown [dependency] key"); } } } - if (first) return set_err(err, errcap, "bad package manifest magic/version"); + if (first) + return dist_set_err(err, errcap, "bad package manifest magic/version"); if (finalize_package_section3(sec, seen, err, errcap) != DIST_OK) return DIST_ERR; return dist_package_manifest_validate(m, err, errcap); diff --git a/src/dist/manifest.h b/src/dist/manifest.h @@ -7,47 +7,15 @@ #include "dist.h" -/* The signed logical package object for distribution v2. The physical - * encodings (.tar.gz and .kpkg) both carry these literal bytes and the same - * detached minisign signature. */ +/* The signed logical package object for distribution. The physical encodings + * (.tar.gz and .kpkg) both carry these literal bytes and the same detached + * minisign signature. */ -#define DIST_MANIFEST_MAGIC "kit-package 2" -#define DIST_MANIFEST_HASH "blake2b-merkle-v1" #define DIST_PACKAGE3_MAGIC "kit-package 3" #define DIST_PACKAGE3_HASH "blake2b-256" #define DIST_PACKAGE3_TREE_FORMAT "kit-tree-v1" #define DIST_PACKAGE3_BLOB_FORMAT "kit-blob-v1" -typedef struct DistArtifact { - uint64_t id; - char path[DIST_PATH_MAX + 1]; - char target[DIST_TRIPLE_MAX]; /* "" = target-independent */ - char kind[DIST_KIND_MAX]; - uint8_t blake2b[DIST_BLAKE2B_LEN]; - uint8_t root[DIST_BLAKE2B_LEN]; - uint64_t size; - int entry; -} DistArtifact; - -typedef struct DistDependency { - char name[DIST_NAME_MAX]; - char version[DIST_PCONSTRAINT_MAX]; - uint8_t blake2b[DIST_BLAKE2B_LEN]; - int has_blake2b; - uint8_t keyid[DIST_KEYID_LEN]; - int has_keyid; -} DistDependency; - -typedef struct DistManifest { - char name[DIST_NAME_MAX]; - char version[DIST_VERSION_MAX]; - char description[DIST_DESC_MAX]; /* "" = absent */ - DistArtifact artifacts[DIST_MAX_ARTIFACTS]; - size_t n_artifacts; - DistDependency deps[DIST_MAX_DEPS]; - size_t n_deps; -} DistManifest; - typedef struct DistPackageOutput { uint64_t id; char name[DIST_NAME_MAX]; @@ -84,13 +52,8 @@ typedef struct DistPackageManifest { size_t n_deps; } DistPackageManifest; -int dist_manifest_emit(const DistManifest* m, KitWriter* out); - int dist_manifest_path_valid(const char* path); -int dist_manifest_parse(const uint8_t* data, size_t len, DistManifest* m, - char* err, size_t errcap); - int dist_package_manifest_emit(const DistPackageManifest* m, KitWriter* out); int dist_package_manifest_parse(const uint8_t* data, size_t len, DistPackageManifest* m, char* err, diff --git a/src/dist/tree.c b/src/dist/tree.c @@ -5,8 +5,7 @@ #include <string.h> #include "blake2b.h" - -#define TREE_LINE_MAX 1024u +#include "dist_parse.h" #define F_HASH 0x01u #define F_TOP_BLOB 0x02u @@ -18,11 +17,6 @@ typedef enum TreeSection { TREE_SEC_TOP, TREE_SEC_FILE } TreeSection; -static int set_err(char* err, size_t cap, const char* msg) { - if (err && cap) snprintf(err, cap, "%s", msg); - return DIST_ERR; -} - int dist_tree_mode_parse(const char* s, uint8_t* out) { if (!s || !out) return DIST_ERR; if (strcmp(s, "-") == 0) { @@ -67,17 +61,17 @@ static int entry_cmp(const void* ap, const void* bp) { int dist_tree_sort_validate(DistTree* tree, char* err, size_t errcap) { size_t i; - if (!tree) return set_err(err, errcap, "missing tree"); + if (!tree) return dist_set_err(err, errcap, "missing tree"); if (tree->n_entries && !tree->entries) - return set_err(err, errcap, "missing tree entries"); + return dist_set_err(err, errcap, "missing tree entries"); qsort(tree->entries, tree->n_entries, sizeof tree->entries[0], entry_cmp); for (i = 0; i < tree->n_entries; ++i) { if (!dist_tree_path_valid(tree->entries[i].path)) - return set_err(err, errcap, "unsafe tree path"); + return dist_set_err(err, errcap, "unsafe tree path"); if (!dist_tree_mode_name(tree->entries[i].mode)) - return set_err(err, errcap, "bad tree mode"); + return dist_set_err(err, errcap, "bad tree mode"); if (i > 0 && strcmp(tree->entries[i - 1u].path, tree->entries[i].path) == 0) - return set_err(err, errcap, "duplicate tree path"); + return dist_set_err(err, errcap, "duplicate tree path"); } return DIST_OK; } @@ -99,37 +93,32 @@ static int emit(KitWriter* out, const char* s) { return kit_writer_write(out, s, strlen(s)) == KIT_OK ? DIST_OK : DIST_ERR; } -static int emit_kv(KitWriter* out, const char* key, const char* val) { - char line[TREE_LINE_MAX]; - snprintf(line, sizeof line, "%s = %s\n", key, val); - return emit(out, line); -} - static int emit_u64(KitWriter* out, const char* key, uint64_t v) { char num[24]; snprintf(num, sizeof num, "%llu", (unsigned long long)v); - return emit_kv(out, key, num); + return dist_emit_kv(out, key, num); } static int emit_hex(KitWriter* out, const char* key, const uint8_t* h) { char hex[2 * DIST_BLAKE2B_LEN + 1]; dist_hex_encode(hex, h, DIST_BLAKE2B_LEN); - return emit_kv(out, key, hex); + return dist_emit_kv(out, key, hex); } int dist_tree_emit(const DistTree* tree, KitWriter* out) { size_t i; if (!out || tree_validate_canonical(tree) != DIST_OK) return DIST_ERR; if (emit(out, DIST_TREE_MAGIC "\n") != DIST_OK) return DIST_ERR; - if (emit_kv(out, "hash", DIST_TREE_HASH) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "blob", DIST_TREE_BLOB_FORMAT) != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "hash", DIST_TREE_HASH) != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "blob", DIST_TREE_BLOB_FORMAT) != DIST_OK) + return DIST_ERR; for (i = 0; i < tree->n_entries; ++i) { const DistTreeEntry* e = &tree->entries[i]; const char* mode = dist_tree_mode_name(e->mode); if (!mode) return DIST_ERR; if (emit(out, "\n[file]\n") != DIST_OK) return DIST_ERR; - if (emit_kv(out, "path", e->path) != DIST_OK) return DIST_ERR; - if (emit_kv(out, "mode", mode) != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "path", e->path) != DIST_OK) return DIST_ERR; + if (dist_emit_kv(out, "mode", mode) != DIST_OK) return DIST_ERR; if (emit_u64(out, "size", e->size) != DIST_OK) return DIST_ERR; if (emit_hex(out, "blob", e->blob) != DIST_OK) return DIST_ERR; if (emit_hex(out, "root", e->root) != DIST_OK) return DIST_ERR; @@ -137,47 +126,13 @@ int dist_tree_emit(const DistTree* tree, KitWriter* out) { return DIST_OK; } -static char* trim_lead(char* s) { - while (*s == ' ' || *s == '\t') ++s; - return s; -} - -static void trim_trail(char* s) { - size_t n = strlen(s); - while (n && (s[n - 1] == ' ' || s[n - 1] == '\t' || s[n - 1] == '\r' || - s[n - 1] == '\n')) - s[--n] = '\0'; -} - -static int copy_field(char* dst, size_t cap, const char* src, char* err, - size_t errcap) { - if (strlen(src) >= cap) return set_err(err, errcap, "field value too long"); - snprintf(dst, cap, "%s", src); - return DIST_OK; -} - -static int parse_u64(const char* s, uint64_t* out) { - uint64_t v = 0; - size_t i; - if (!s || !*s || !out) return DIST_ERR; - for (i = 0; s[i]; ++i) { - unsigned d; - if (s[i] < '0' || s[i] > '9') return DIST_ERR; - d = (unsigned)(s[i] - '0'); - if (v > (UINT64_MAX - d) / 10u) return DIST_ERR; - v = v * 10u + d; - } - *out = v; - return DIST_OK; -} - static int hex_hash(const char* val, uint8_t out[DIST_BLAKE2B_LEN]) { if (strlen(val) != 2u * DIST_BLAKE2B_LEN) return DIST_ERR; return dist_hex_decode(out, val, DIST_BLAKE2B_LEN); } static int check_dup(uint32_t seen, uint32_t flag, char* err, size_t errcap) { - if (seen & flag) return set_err(err, errcap, "duplicate tree field"); + if (seen & flag) return dist_set_err(err, errcap, "duplicate tree field"); return DIST_OK; } @@ -186,19 +141,20 @@ static int finalize_file(const DistTree* tree, uint32_t seen, char* err, const DistTreeEntry* e; if ((seen & (F_PATH | F_MODE | F_SIZE | F_FILE_BLOB | F_ROOT)) != (F_PATH | F_MODE | F_SIZE | F_FILE_BLOB | F_ROOT)) - return set_err(err, errcap, "missing required [file] field"); + return dist_set_err(err, errcap, "missing required [file] field"); if (!tree || tree->n_entries == 0) - return set_err(err, errcap, "missing file"); + return dist_set_err(err, errcap, "missing file"); e = &tree->entries[tree->n_entries - 1u]; if (!dist_tree_path_valid(e->path)) - return set_err(err, errcap, "unsafe tree path"); + return dist_set_err(err, errcap, "unsafe tree path"); if (!dist_tree_mode_name(e->mode)) - return set_err(err, errcap, "bad tree mode"); + return dist_set_err(err, errcap, "bad tree mode"); if (tree->n_entries > 1u) { const char* prev = tree->entries[tree->n_entries - 2u].path; int cmp = strcmp(prev, e->path); - if (cmp == 0) return set_err(err, errcap, "duplicate tree path"); - if (cmp > 0) return set_err(err, errcap, "non-canonical tree ordering"); + if (cmp == 0) return dist_set_err(err, errcap, "duplicate tree path"); + if (cmp > 0) + return dist_set_err(err, errcap, "non-canonical tree ordering"); } return DIST_OK; } @@ -211,48 +167,48 @@ int dist_tree_parse(const uint8_t* data, size_t len, DistTree* out, char* err, uint32_t top_seen = 0; uint32_t file_seen = 0; - if (!data || !out) return set_err(err, errcap, "missing tree manifest"); + if (!data || !out) return dist_set_err(err, errcap, "missing tree manifest"); if (out->cap_entries && !out->entries) - return set_err(err, errcap, "missing tree entries"); + return dist_set_err(err, errcap, "missing tree entries"); out->n_entries = 0; while (pos < len) { - char buf[TREE_LINE_MAX]; + char buf[DIST_KV_LINE_MAX]; size_t end = pos; size_t n, i; char *t, *key, *val, *eq; while (end < len && data[end] != '\n') ++end; n = end - pos; - if (n >= sizeof buf) return set_err(err, errcap, "line too long"); + if (n >= sizeof buf) return dist_set_err(err, errcap, "line too long"); for (i = pos; i < end; ++i) if (data[i] == 0) - return set_err(err, errcap, "NUL byte in tree manifest"); + return dist_set_err(err, errcap, "NUL byte in tree manifest"); memcpy(buf, data + pos, n); buf[n] = '\0'; pos = (end < len) ? end + 1u : end; - trim_trail(buf); + dist_trim_trail(buf); if (first) { first = 0; if (strcmp(buf, DIST_TREE_MAGIC) != 0) - return set_err(err, errcap, "bad tree magic/version"); + return dist_set_err(err, errcap, "bad tree magic/version"); continue; } - t = trim_lead(buf); + t = dist_trim_lead(buf); if (*t == '\0' || *t == '#') continue; if (*t == '[') { if (strcmp(t, "[file]") != 0) - return set_err(err, errcap, "unknown tree section"); + return dist_set_err(err, errcap, "unknown tree section"); if ((top_seen & (F_HASH | F_TOP_BLOB)) != (F_HASH | F_TOP_BLOB)) - return set_err(err, errcap, "missing required top-level field"); + return dist_set_err(err, errcap, "missing required top-level field"); if (sec == TREE_SEC_FILE && finalize_file(out, file_seen, err, errcap) != DIST_OK) return DIST_ERR; if (out->n_entries >= out->cap_entries) - return set_err(err, errcap, "too many tree files"); + return dist_set_err(err, errcap, "too many tree files"); memset(&out->entries[out->n_entries], 0, sizeof out->entries[0]); ++out->n_entries; sec = TREE_SEC_FILE; @@ -261,27 +217,27 @@ int dist_tree_parse(const uint8_t* data, size_t len, DistTree* out, char* err, } eq = strchr(t, '='); - if (!eq) return set_err(err, errcap, "expected key = value"); + if (!eq) return dist_set_err(err, errcap, "expected key = value"); *eq = '\0'; key = t; - trim_trail(key); - val = trim_lead(eq + 1); + dist_trim_trail(key); + val = dist_trim_lead(eq + 1); if (sec == TREE_SEC_TOP) { if (strcmp(key, "hash") == 0) { if (check_dup(top_seen, F_HASH, err, errcap) != DIST_OK) return DIST_ERR; if (strcmp(val, DIST_TREE_HASH) != 0) - return set_err(err, errcap, "unsupported tree hash"); + return dist_set_err(err, errcap, "unsupported tree hash"); top_seen |= F_HASH; } else if (strcmp(key, "blob") == 0) { if (check_dup(top_seen, F_TOP_BLOB, err, errcap) != DIST_OK) return DIST_ERR; if (strcmp(val, DIST_TREE_BLOB_FORMAT) != 0) - return set_err(err, errcap, "unsupported tree blob format"); + return dist_set_err(err, errcap, "unsupported tree blob format"); top_seen |= F_TOP_BLOB; } else { - return set_err(err, errcap, "unknown top-level tree key"); + return dist_set_err(err, errcap, "unknown top-level tree key"); } } else { DistTreeEntry* e = &out->entries[out->n_entries - 1u]; @@ -289,43 +245,44 @@ int dist_tree_parse(const uint8_t* data, size_t len, DistTree* out, char* err, if (check_dup(file_seen, F_PATH, err, errcap) != DIST_OK) return DIST_ERR; if (!dist_tree_path_valid(val)) - return set_err(err, errcap, "unsafe tree path"); - if (copy_field(e->path, sizeof e->path, val, err, errcap) != DIST_OK) + return dist_set_err(err, errcap, "unsafe tree path"); + if (dist_copy_field(e->path, sizeof e->path, val, err, errcap) != + DIST_OK) return DIST_ERR; file_seen |= F_PATH; } else if (strcmp(key, "mode") == 0) { if (check_dup(file_seen, F_MODE, err, errcap) != DIST_OK) return DIST_ERR; if (dist_tree_mode_parse(val, &e->mode) != DIST_OK) - return set_err(err, errcap, "bad tree mode"); + return dist_set_err(err, errcap, "bad tree mode"); file_seen |= F_MODE; } else if (strcmp(key, "size") == 0) { if (check_dup(file_seen, F_SIZE, err, errcap) != DIST_OK) return DIST_ERR; - if (parse_u64(val, &e->size) != DIST_OK) - return set_err(err, errcap, "bad tree size"); + if (dist_parse_u64(val, &e->size) != DIST_OK) + return dist_set_err(err, errcap, "bad tree size"); file_seen |= F_SIZE; } else if (strcmp(key, "blob") == 0) { if (check_dup(file_seen, F_FILE_BLOB, err, errcap) != DIST_OK) return DIST_ERR; if (hex_hash(val, e->blob) != DIST_OK) - return set_err(err, errcap, "bad tree blob hash"); + return dist_set_err(err, errcap, "bad tree blob hash"); file_seen |= F_FILE_BLOB; } else if (strcmp(key, "root") == 0) { if (check_dup(file_seen, F_ROOT, err, errcap) != DIST_OK) return DIST_ERR; if (hex_hash(val, e->root) != DIST_OK) - return set_err(err, errcap, "bad tree blob root"); + return dist_set_err(err, errcap, "bad tree blob root"); file_seen |= F_ROOT; } else { - return set_err(err, errcap, "unknown [file] tree key"); + return dist_set_err(err, errcap, "unknown [file] tree key"); } } } - if (first) return set_err(err, errcap, "bad tree magic/version"); + if (first) return dist_set_err(err, errcap, "bad tree magic/version"); if ((top_seen & (F_HASH | F_TOP_BLOB)) != (F_HASH | F_TOP_BLOB)) - return set_err(err, errcap, "missing required top-level field"); + return dist_set_err(err, errcap, "missing required top-level field"); if (sec == TREE_SEC_FILE && finalize_file(out, file_seen, err, errcap) != DIST_OK) return DIST_ERR; @@ -334,6 +291,9 @@ int dist_tree_parse(const uint8_t* data, size_t len, DistTree* out, char* err, void dist_tree_id(uint8_t out[DIST_BLAKE2B_LEN], const uint8_t* manifest, size_t len) { + /* Plain BLAKE2b of the canonical tree bytes: this id is the package + * format's tree-object hash (verified via pkg_hash), so it must not be + * domain-prefixed. See dist_blob_id. */ dist_blake2b(out, manifest, len); } diff --git a/src/emu/emu.h b/src/emu/emu.h @@ -125,8 +125,12 @@ typedef struct EmuImportBinding { KitEmuImportSignature signature; } EmuImportBinding; +/* Max DT_NEEDED entries recorded per object; overflow is silently truncated by + * the ELF emu loader (src/obj/elf/emu_load.c). */ +#define EMU_MAX_NEEDED 16 + typedef struct EmuObjectImports { - KitSlice needed[16]; + KitSlice needed[EMU_MAX_NEEDED]; u32 nneeded; } EmuObjectImports; @@ -393,7 +397,6 @@ KitStatus emu_addr_space_find_gap(EmuAddrSpace*, u64 nbytes, u64 align, KitStatus emu_addr_space_set_brk(EmuAddrSpace*, u64 requested, u64* actual_out); KitStatus emu_addr_space_copy_in(EmuAddrSpace*, u64 va, const void* src, u64 nbytes); -KitStatus emu_addr_space_set_perm(EmuAddrSpace*, u64 va, u64 nbytes, u8 perms); u8* emu_addr_space_ptr(EmuAddrSpace*, u64 va, u64 nbytes, u8 need_perms); u64 emu_addr_space_contig_len(EmuAddrSpace*, u64 va, u8 need_perms); const EmuMemFault* emu_addr_space_last_fault(const EmuAddrSpace*); diff --git a/src/emu/image.c b/src/emu/image.c @@ -330,11 +330,6 @@ KitStatus emu_addr_space_copy_in(EmuAddrSpace* as, u64 va, const void* src, return KIT_OK; } -KitStatus emu_addr_space_set_perm(EmuAddrSpace* as, u64 va, u64 nbytes, - u8 perms) { - return emu_addr_space_protect(as, va, nbytes, perms); -} - u8* emu_addr_space_ptr(EmuAddrSpace* as, u64 va, u64 nbytes, u8 need_perms) { EmuMap* m; u64 off, start_page, end_page, i; diff --git a/src/emu/runtime.c b/src/emu/runtime.c @@ -220,7 +220,7 @@ static u64 emu_mem_load_checked(EmuThread* t, u64 addr, u64 nbytes, u8 access, return 0; } -/* Bounds-checked fast (unchecked-resume) load/store of `nbytes` (1..4) +/* Bounds-checked fast (unchecked-resume) load/store of `nbytes` (1..8) * little-endian bytes through the CPUState guest-AS window. A load miss * trap-faults the CPU and yields 0; a store miss routes through the OS fault * delivery and returns its resume PC. These back the fixed-width shims. */ @@ -258,11 +258,7 @@ u32 emu_mem_load32(EmuThread* t, u64 addr) { return (u32)emu_mem_load_raw(t, addr, 4u); } u64 emu_mem_load64(EmuThread* t, u64 addr) { - /* Two-half compose preserves the legacy per-map-boundary translation: each - * half is bounds-checked through its own va_to_host_perm window. */ - u32 lo = emu_mem_load32(t, addr); - u32 hi = emu_mem_load32(t, addr + 4u); - return (u64)lo | ((u64)hi << 32); + return emu_mem_load_raw(t, addr, 8u); } u64 emu_mem_load8_checked(EmuThread* t, u64 addr, u64 fault_pc, u64 next_pc, diff --git a/src/interp/engine.c b/src/interp/engine.c @@ -216,6 +216,11 @@ static int interp_intrinsic(InterpStack* st, InterpFunc* fn, u64* regs, * follow strict stack discipline (CALL bumps the top, RET rewinds it), so a * generous fixed reservation suffices; overflow traps cleanly as a stack * overflow rather than corrupting memory. */ +/* TODO(perf): these 16 MiB are allocated and freed per kit_interp_call / + * kit_interp_call_args (each spins up a fresh InterpStack). For call-heavy + * embeddings, reuse the arenas across calls via a per-program stack pool rather + * than shrinking the reservation — the size must stay generous because the + * arenas are non-relocating (escaping pointers; see above). */ #define INTERP_REGS_RESERVE (8u * 1024u * 1024u) #define INTERP_MEM_RESERVE (8u * 1024u * 1024u) @@ -921,22 +926,22 @@ KitInterpStatus interp_run_stack(InterpStack* st, int64_t* out_ret) { /* Per-function lazy threading: copy each opcode's handler into its record on * first entry to the function (RELOAD runs whenever the top frame changes). */ -#define RELOAD() \ - do { \ - fr = &st->frames[st->nframes - 1u]; \ - fn = fr->fn; \ - regs = (u64*)(st->regs_arena + fr->regs_off); \ - mem_off = fr->mem_off; \ - ip = fr->ip; \ - if (!fn->threaded) { \ - u32 ti_; \ - for (ti_ = 0; ti_ < fn->ncode; ++ti_) { \ - u32 o_ = fn->code[ti_].op; \ - fn->code[ti_].handler = \ - g_dt[o_ < (u32)IOP__COUNT ? o_ : (u32)IOP_TRAP]; \ - } \ - fn->threaded = 1; \ - } \ +#define RELOAD() \ + do { \ + fr = &st->frames[st->nframes - 1u]; \ + fn = fr->fn; \ + regs = (u64*)(st->regs_arena + fr->regs_off); \ + mem_off = fr->mem_off; \ + ip = fr->ip; \ + if (!fn->threaded) { \ + u32 ti_; \ + for (ti_ = 0; ti_ < fn->ncode; ++ti_) { \ + u32 o_ = fn->code[ti_].op; \ + fn->code[ti_].handler = \ + st->dt[o_ < (u32)IOP__COUNT ? o_ : (u32)IOP_TRAP]; \ + } \ + fn->threaded = 1; \ + } \ } while (0) #else #define RELOAD() \ @@ -950,13 +955,11 @@ KitInterpStatus interp_run_stack(InterpStack* st, int64_t* out_ret) { #endif #if INTERP_DISPATCH_THREADED - static void* g_dt[IOP__COUNT]; - static int g_dt_ready = 0; - if (!g_dt_ready) { -#define DT_ENTRY(name) g_dt[name] = &&L_##name; + if (!st->dt_ready) { +#define DT_ENTRY(name) st->dt[name] = &&L_##name; INTERP_OPS(DT_ENTRY) #undef DT_ENTRY - g_dt_ready = 1; + st->dt_ready = 1; } #endif diff --git a/src/interp/interp.h b/src/interp/interp.h @@ -182,6 +182,14 @@ struct KitInterpStack { u8 status; /* KitInterpStatus */ u8 mem_fault; /* set by mem_read/write/copy on a translation miss */ const char* trap_reason; + /* Computed-goto dispatch table (threaded build only): per-context, populated + * lazily on first interp_run_stack entry. Lives here rather than as a + * function-local static so there is no global state / first-call race. The + * label addresses are identical across contexts, so the per-function handler + * cache (InterpFunc.threaded) stays consistent regardless of which stack + * threaded a given function. */ + void* dt[IOP__COUNT]; + u8 dt_ready; }; typedef struct KitInterpStack InterpStack; diff --git a/src/link/link.c b/src/link/link.c @@ -143,7 +143,7 @@ LinkInputId link_add_obj_bytes(Linker* l, const char* name, const u8* data, in = inputs_push(l, &id); in->order = l->next_input_order++; in->obj = ob; /* re-uses the ObjBuilder slot for ownership */ - in->name = name ? pool_intern_slice(l->c->global, slice_from_cstr(name)) : 0; + in->name = name ? pool_intern_cstr(l->c->global, name) : 0; { Sym soname = 0; if (impl->classify_obj_input && @@ -185,7 +185,7 @@ LinkInputId link_add_dso_bytes(Linker* l, const char* name, const u8* data, in->kind = LINK_INPUT_DSO_BYTES; in->order = l->next_input_order++; in->obj = ob; - in->name = name ? pool_intern_slice(l->c->global, slice_from_cstr(name)) : 0; + in->name = name ? pool_intern_cstr(l->c->global, name) : 0; /* DT_SONAME wins; fall back to the file's basename if the DSO has * no SONAME (matches GNU ld's behaviour for hand-rolled libraries * that forgot to set DT_SONAME). */ @@ -196,7 +196,7 @@ LinkInputId link_add_dso_bytes(Linker* l, const char* name, const u8* data, const char* p; for (p = name; *p; ++p) if (*p == '/') base = p + 1; - in->soname = pool_intern_slice(l->c->global, slice_from_cstr(base)); + in->soname = pool_intern_cstr(l->c->global, base); } else { in->soname = 0; } @@ -239,7 +239,7 @@ LinkInputId link_add_archive_bytes(Linker* l, const char* name, const u8* data, ar = LinkArchives_push(&l->archives, NULL); if (!ar) compiler_panic(l->c, SRCLOC_NONE, "link: out of memory growing archives"); - ar->name = name ? pool_intern_slice(l->c->global, slice_from_cstr(name)) : 0; + ar->name = name ? pool_intern_cstr(l->c->global, name) : 0; ar->order = l->next_input_order++; ar->whole_archive = whole_archive; ar->link_mode = link_mode; diff --git a/src/link/link_internal.h b/src/link/link_internal.h @@ -387,130 +387,12 @@ SEGVEC_DEFINE(LinkRelocs, LinkRelocApply, 7); /* 128 entries per segment */ /* ---- Dynamic-link synthesis state (Phase 4) ---- * - * Owned by LinkImage when emit_pie is set. Holds the synthesized - * .interp / .dynsym / .dynstr / .gnu.hash / .rela.dyn / .rela.plt / - * .plt / .got.plt / .dynamic content plus the section ids the emit - * pass needs to fill PT_DYNAMIC and the .dynamic body. - * - * Phase 4 builds the dynsym/dynstr/gnu.hash content and the JUMP_SLOT - * .rela.plt records (one per imported function, against its synthetic - * .got.plt slot). The .plt body is allocated but not emitted (Phase 5). - * Phase 6 populates .rela.dyn with R_AARCH64_RELATIVE records for any - * internal absolute reloc seen during reloc-apply. - * - * Layout invariants this struct enforces: - * - dynsym entry 0 is the reserved STN_UNDEF slot (zero-filled). - * - dynsym entries 1..nimport_func+nimport_data are imports, in the - * order PLT-functions first, then GOT-data. - * - PLT slots and JUMP_SLOT entries match the import_func order 1:1. - * - .got.plt has 3 reserved leading u64 slots (per AArch64 psABI: - * slot 0 = &.dynamic, slot 1 = link_map cookie, slot 2 = - * _dl_runtime_resolve), then one slot per imported function. - */ - -typedef struct DynSymRec { - u32 st_name; /* offset into .dynstr */ - u8 st_info; - u8 st_other; - u16 st_shndx; - u64 st_value; - u64 st_size; -} DynSymRec; - -typedef struct DynRela { - u64 r_offset; /* image-relative vaddr of the patch site */ - u64 r_info; /* ELF64_R_INFO(dynsym_index, elf_reloc_type) */ - i64 r_addend; -} DynRela; - -typedef struct LinkDynState { - /* PT_INTERP / .interp. interp_path is interned in compiler->global. */ - Sym interp_path; - LinkSectionId sec_interp; - - /* .dynsym */ - LinkSectionId sec_dynsym; - DynSymRec* dynsym; - u32 ndynsym; /* incl. slot-0 STN_UNDEF */ - u32 first_global; /* sh_info value: index of first non-local entry */ - - /* .dynstr */ - LinkSectionId sec_dynstr; - u8* dynstr; - u32 dynstr_len; - - /* .gnu.hash */ - LinkSectionId sec_gnu_hash; - u8* gnu_hash; - u32 gnu_hash_len; - - /* GNU symbol versioning. Emitted only when at least one imported symbol - * binds to a versioned DSO export (nverneed > 0); otherwise all three are - * zero and no version sections / DT_VER* entries are produced (musl/static - * links are unchanged). .gnu.version is one u16 per .dynsym entry; - * .gnu.version_r holds Verneed/Vernaux requirements keyed by DT_NEEDED - * soname. Both carry only .dynstr offsets + version indices (no vaddrs), so - * the bytes are final at layout time and copied verbatim during emit. */ - LinkSectionId sec_gnu_version; - u8* versym; /* ndynsym * 2 bytes */ - u32 versym_len; - LinkSectionId sec_gnu_version_r; - u8* verneed; /* nverneed Verneed records + their Vernaux */ - u32 verneed_len; - u32 nverneed; /* DT_VERNEEDNUM */ - - /* .rela.dyn — R_AARCH64_GLOB_DAT (imports against .got slots) and - * R_AARCH64_RELATIVE (PIE internal abs64 fixups, populated during - * Phase 6 emit). Pre-sized at layout time; the RELATIVE tail is - * filled in during emit. */ - LinkSectionId sec_rela_dyn; - DynRela* rela_dyn; - u32 nrela_dyn; /* number of records currently populated */ - u32 cap_rela_dyn; /* allocation capacity (records, not bytes) */ - - /* .rela.plt — R_AARCH64_JUMP_SLOT, one per imported function. */ - LinkSectionId sec_rela_plt; - DynRela* rela_plt; - u32 nrela_plt; - - /* .plt — 32-byte PLT0 stub + 16 bytes per imported function. Body - * is allocated (zero-initialized) but not emitted in Phase 4. */ - LinkSectionId sec_plt; - u32 nplt; /* number of imported functions */ - u64 plt_vaddr; /* image-relative .plt base */ - u64 plt_size; - - /* .got.plt — 24 reserved bytes + 8 per PLT slot. */ - LinkSectionId sec_got_plt; - u64 got_plt_vaddr; - u64 got_plt_size; - - /* .dynamic — PT_DYNAMIC body. Built at layout time; its size is - * fixed once we know the DT_NEEDED count. */ - LinkSectionId sec_dynamic; - u64 dynamic_vaddr; - u64 dynamic_size; - u32 ndyn_entries; - - /* DT_NEEDED list (interned soname Syms, in input order). */ - Sym* needed; - u32 nneeded; - - /* Per-import dynsym index, indexed by LinkSymId. 0 means "not - * imported / not in dynsym". Used by GLOB_DAT / JUMP_SLOT emit. */ - u32* sym_dynidx; /* size = sym_dynidx_size */ - u32 sym_dynidx_size; - - /* Per-import PLT entry vaddr, indexed by LinkSymId (Phase 5). Set - * for every imported function: vaddr of its 16-byte PLT stub inside - * `.plt`. 0 means "no PLT stub" (symbol is data-only or not - * imported). apply_all_relocs reads this when redirecting a - * CALL26/JUMP26 against an imported function — S becomes the PLT - * entry vaddr instead of the symbol's (zero) vaddr. The vaddrs - * stored here track the post-shift values (shift_image_addresses - * bumps them along with .plt's segment vaddr). */ - u64* sym_plt_vaddr; /* size = sym_dynidx_size */ -} LinkDynState; + * The ELF linker's dynamic-link working state (.dynsym / .dynstr / .gnu.hash + * / .rela.* / .plt / .got.plt / .dynamic) and its wire-format records live in + * the ELF-only header src/obj/elf/link_dyn.h. LinkImage holds it as an opaque + * `LinkDynState* dyn` so the COFF/Mach-O linkers never see the ELF field + * names; only the ELF linker dereferences it. */ +typedef struct LinkDynState LinkDynState; struct LinkImage { Compiler* c; diff --git a/src/link/link_jit.c b/src/link/link_jit.c @@ -1310,7 +1310,7 @@ static ViewSec* view_sec_for(KitJit* jit, ViewSec* tab, u32* ntab, ViewSec** tab_out) { Heap* h = (Heap*)jit->c->ctx->heap; Pool* view_pool = obj_compiler(view_ob)->global; - Sym vn = pool_intern_slice(view_pool, slice_from_cstr(name)); + Sym vn = pool_intern_cstr(view_pool, name); u32 i; for (i = 0; i < *ntab; ++i) { if (tab[i].view_name == vn) { diff --git a/src/link/link_layout.c b/src/link/link_layout.c @@ -50,7 +50,9 @@ int link_section_kept(const Section* s) { if (!(s->flags & SF_ALLOC)) return 0; if (s->sem == SSEM_PROGBITS || s->sem == SSEM_NOBITS || s->sem == SSEM_NOTE) return 1; - if (s->sem == SSEM_INIT_ARRAY || s->sem == SSEM_FINI_ARRAY) return 1; + if (s->sem == SSEM_INIT_ARRAY || s->sem == SSEM_FINI_ARRAY || + s->sem == SSEM_PREINIT_ARRAY) + return 1; return 0; } diff --git a/src/obj/bytebuf.h b/src/obj/bytebuf.h @@ -0,0 +1,111 @@ +#ifndef KIT_OBJ_BYTEBUF_H +#define KIT_OBJ_BYTEBUF_H + +/* Heap-backed growable byte buffer — the canonical staging buffer for object + * writers that assemble a blob (a linkedit table, a .dynstr, a chained-fixups + * stream) before handing it to a Writer. The ELF and Mach-O linkers both grew + * their own near-identical copies; this is the shared one. + * + * Offsets returned by the append family are byte offsets into the buffer at + * the time of the call, which callers patch up later via the data/len fields. + * Allocation failure is fatal inside VEC_GROW (it panics), so callers do not + * need to check append return values for OOM. */ + +#include <string.h> + +#include "core/core.h" +#include "core/bytes.h" +#include "core/util.h" +#include "core/vec.h" + +typedef struct ObjByteBuf { + Heap* heap; + u8* data; + u32 len; + u32 cap; +} ObjByteBuf; + +static inline void objbb_init(ObjByteBuf* b, Heap* h) { + b->heap = h; + b->data = NULL; + b->len = 0; + b->cap = 0; +} + +static inline void objbb_fini(ObjByteBuf* b) { + if (b->data) b->heap->free(b->heap, b->data, b->cap); + b->data = NULL; + b->cap = b->len = 0; +} + +static inline void objbb_reserve(ObjByteBuf* b, u32 need) { + if (need <= b->cap) return; + (void)VEC_GROW(b->heap, b->data, b->cap, need); +} + +/* Zero-fill up to the next `a`-aligned length; returns the new (aligned) + * length so callers can record an aligned offset in one step. */ +static inline u32 objbb_align(ObjByteBuf* b, u32 a) { + u32 n = (u32)ALIGN_UP((u64)b->len, (u64)a); + if (n > b->len) { + objbb_reserve(b, n); + memset(b->data + b->len, 0, n - b->len); + b->len = n; + } + return b->len; +} + +static inline u32 objbb_append(ObjByteBuf* b, const void* src, u32 n) { + u32 off = b->len; + objbb_reserve(b, b->len + n); + if (n) memcpy(b->data + b->len, src, n); + b->len += n; + return off; +} + +static inline u32 objbb_u8(ObjByteBuf* b, u8 v) { return objbb_append(b, &v, 1); } + +static inline u32 objbb_u16(ObjByteBuf* b, u16 v) { + u8 t[2]; + wr_u16_le(t, v); + return objbb_append(b, t, 2); +} + +static inline u32 objbb_u32(ObjByteBuf* b, u32 v) { + u8 t[4]; + wr_u32_le(t, v); + return objbb_append(b, t, 4); +} + +static inline u32 objbb_u64(ObjByteBuf* b, u64 v) { + u8 t[8]; + wr_u64_le(t, v); + return objbb_append(b, t, 8); +} + +/* Append n bytes plus a NUL terminator (no dedup). */ +static inline u32 objbb_str(ObjByteBuf* b, const char* s, u32 n) { + u32 off = b->len; + objbb_reserve(b, b->len + n + 1u); + if (n) memcpy(b->data + b->len, s, n); + b->data[b->len + n] = 0; + b->len += n + 1u; + return off; +} + +/* Append a NUL-terminated string with linear dedup over what we've appended + * so far. Strtabs (.dynstr) are small, so the scan stays cheap. An empty + * string maps to offset 0 (the leading NUL the caller is expected to have + * placed). */ +static inline u32 objbb_append_str(ObjByteBuf* b, const char* s, u32 n) { + if (n == 0) return 0; + if (b->len > n) { + u32 i; + for (i = 0; i + n < b->len; ++i) { + if (b->data[i + n] == 0 && memcmp(b->data + i, s, n) == 0) return i; + } + } + return objbb_str(b, s, n); +} + +#endif diff --git a/src/obj/coff/link.c b/src/obj/coff/link.c @@ -225,7 +225,7 @@ typedef struct CoffTlsLayout { } CoffTlsLayout; static LinkSymId coff_find_sym(LinkImage* img, const char* name) { - Sym sym = pool_intern_slice(img->c->global, slice_from_cstr(name)); + Sym sym = pool_intern_cstr(img->c->global, name); u32 n = LinkSyms_count(&img->syms); u32 i; for (i = 0; i < n; ++i) { diff --git a/src/obj/elf/emu_load.c b/src/obj/elf/emu_load.c @@ -132,7 +132,7 @@ static KitStatus parse_object_dynamic(EmuLoadedImage* img, u8* dyn; u64 dynamic_size; u64 strtab = 0, strsz = 0; - u64 needed_offs[16]; + u64 needed_offs[EMU_MAX_NEEDED]; u32 nneeded_offs = 0; u64 j; EmuElfDynInfo* dinfo = elf_dyn(obj); @@ -148,8 +148,12 @@ static KitStatus parse_object_dynamic(EmuLoadedImage* img, if (tag == DT_NULL) break; switch (tag) { case DT_NEEDED: - if (nneeded_offs < sizeof(needed_offs) / sizeof(needed_offs[0])) + if (nneeded_offs < EMU_MAX_NEEDED) needed_offs[nneeded_offs++] = val; + else + KIT_LOGW("emu: >%d DT_NEEDED entries; extra shared-library deps " + "ignored", + EMU_MAX_NEEDED); break; case DT_STRTAB: strtab = ptr; @@ -222,8 +226,7 @@ static KitStatus parse_object_dynamic(EmuLoadedImage* img, } for (j = 0; j < nneeded_offs; ++j) { u64 val = needed_offs[j]; - if (strtab && obj->imports.nneeded < sizeof(obj->imports.needed) / - sizeof(obj->imports.needed[0])) { + if (strtab && obj->imports.nneeded < EMU_MAX_NEEDED) { u8* s = emu_addr_space_ptr(&img->addr_space, strtab + val, 1, EMU_MEM_READ); if (s) diff --git a/src/obj/elf/link.c b/src/obj/elf/link.c @@ -57,6 +57,7 @@ #include "link/link_arch.h" #include "link/link_internal.h" #include "obj/elf/elf.h" +#include "obj/elf/link_dyn.h" #include "obj/format.h" /* ---- ELF64 wire structs (subset) ---- */ diff --git a/src/obj/elf/link_dyn.c b/src/obj/elf/link_dyn.c @@ -42,7 +42,9 @@ #include "link/link.h" #include "link/link_arch.h" #include "link/link_internal.h" +#include "obj/bytebuf.h" #include "obj/elf/elf.h" +#include "obj/elf/link_dyn.h" #include "obj/format.h" /* ---- small allocators (mirror layout_iplt's helpers) ---- */ @@ -82,48 +84,7 @@ static u32 dyn_alloc_sections(LinkImage* img, u32 nsec) { return base; } -/* ---- byte-builder for .dynstr / .gnu.hash ---- */ - -typedef struct ByteBuf { - Heap* heap; - u8* data; - u32 len; - u32 cap; -} ByteBuf; - -static void bb_init(ByteBuf* b, Heap* h) { - b->heap = h; - b->data = NULL; - b->len = 0; - b->cap = 0; -} -static void bb_reserve(ByteBuf* b, u32 need) { - if (need <= b->cap) return; - (void)VEC_GROW(b->heap, b->data, b->cap, need); -} -static u32 bb_append(ByteBuf* b, const void* src, u32 n) { - u32 off = b->len; - bb_reserve(b, b->len + n); - if (n) memcpy(b->data + b->len, src, n); - b->len += n; - return off; -} -static u32 bb_append_str(ByteBuf* b, const char* s, u32 n) { - /* Linear dedup over what we've appended so far. Strtabs are small. */ - if (n == 0) return 0; - if (b->len > n) { - u32 i; - for (i = 0; i + n < b->len; ++i) { - if (b->data[i + n] == 0 && memcmp(b->data + i, s, n) == 0) return i; - } - } - u32 off = b->len; - bb_reserve(b, b->len + n + 1u); - memcpy(b->data + b->len, s, n); - b->data[b->len + n] = 0; - b->len += n + 1u; - return off; -} +/* The .dynstr / .gnu.hash byte-builder is the shared ObjByteBuf (objbb_*). */ /* ---- GNU-hash computation (psABI v1 hash) ---- * Body layout: @@ -302,7 +263,7 @@ static void collect_needed(Linker* l, LinkImage* img, LinkDynState* dyn) { * for Scrt1.o's `environ` and `__progname` definitions. */ static void build_dynsym(LinkImage* img, LinkDynState* dyn, - const ImportLists* il, ByteBuf* dynstr) { + const ImportLists* il, ObjByteBuf* dynstr) { Heap* h = img->heap; u32 nimports = il->nfuncs + il->ndatas; u32 ndynsym = 1u + il->nexports + nimports; /* +1 for null slot */ @@ -318,7 +279,7 @@ static void build_dynsym(LinkImage* img, LinkDynState* dyn, * the empty string. */ { u8 z = 0; - bb_append(dynstr, &z, 1); + objbb_append(dynstr, &z, 1); } /* Per-symbol: dedupe `sym_dynidx` lookup table. Sized to LinkSymId @@ -353,7 +314,7 @@ static void build_dynsym(LinkImage* img, LinkDynState* dyn, size_t namelen = nm_s.len; u8 elf_type = elf_st_type(s->kind); u8 elf_bind = elf_st_bind(s->bind); - r->st_name = bb_append_str(dynstr, nm, (u32)namelen); + r->st_name = objbb_append_str(dynstr, nm, (u32)namelen); r->st_info = ELF64_ST_INFO(elf_bind, elf_type); r->st_other = STV_DEFAULT; /* The emitter refreshes defined-symbol values after the final header @@ -373,7 +334,7 @@ static void build_dynsym(LinkImage* img, LinkDynState* dyn, Slice nm_s = pool_slice(img->c->global, s->name); const char* nm = nm_s.s; size_t namelen = nm_s.len; - r->st_name = bb_append_str(dynstr, nm, (u32)namelen); + r->st_name = objbb_append_str(dynstr, nm, (u32)namelen); r->st_info = ELF64_ST_INFO(STB_GLOBAL, STT_FUNC); r->st_other = STV_DEFAULT; r->st_shndx = SHN_UNDEF; @@ -394,7 +355,7 @@ static void build_dynsym(LinkImage* img, LinkDynState* dyn, elf_type = STT_TLS; else if (s->kind == SK_NOTYPE) elf_type = STT_NOTYPE; - r->st_name = bb_append_str(dynstr, nm, (u32)namelen); + r->st_name = objbb_append_str(dynstr, nm, (u32)namelen); r->st_info = ELF64_ST_INFO(STB_GLOBAL, elf_type); r->st_other = STV_DEFAULT; r->st_shndx = SHN_UNDEF; @@ -498,7 +459,7 @@ static void ver_process_import(VerBuild* vb, LinkSymId lsid) { } static void build_versions(Linker* l, LinkImage* img, LinkDynState* dyn, - const ImportLists* il, ByteBuf* dynstr) { + const ImportLists* il, ObjByteBuf* dynstr) { Heap* h = img->heap; VerBuild vb; u32 i; @@ -566,7 +527,7 @@ static void build_versions(Linker* l, LinkImage* img, LinkDynState* dyn, p = vn; for (si = 0; si < nson; ++si) { Slice so_s = pool_slice(l->c->global, sonames[si]); - u32 file_off = bb_append_str(dynstr, so_s.s, (u32)so_s.len); + u32 file_off = objbb_append_str(dynstr, so_s.s, (u32)so_s.len); u8* vn_rec = p; u32 cnt = 0; u8* aux; @@ -577,7 +538,7 @@ static void build_versions(Linker* l, LinkImage* img, LinkDynState* dyn, u32 name_off; if (vb.reqs[r].soname != sonames[si]) continue; ver_s = pool_slice(l->c->global, vb.reqs[r].version); - name_off = bb_append_str(dynstr, ver_s.s, (u32)ver_s.len); + name_off = objbb_append_str(dynstr, ver_s.s, (u32)ver_s.len); wr_u32_le(p + 0, elf_sysv_hash(ver_s.s, (u32)ver_s.len)); /* vna_hash */ wr_u16_le(p + 4, 0); /* vna_flags */ wr_u16_le(p + 6, vb.reqs[r].index); /* vna_other */ @@ -624,7 +585,7 @@ static void build_versions(Linker* l, LinkImage* img, LinkDynState* dyn, * loader's correctness check (false positives only cost a chain scan). */ static void build_gnu_hash(Heap* h, LinkImage* img, LinkDynState* dyn, - const ByteBuf* dynstr) { + const ObjByteBuf* dynstr) { u32 hashed = (dyn->ndynsym > dyn->first_global) ? (dyn->ndynsym - dyn->first_global) : 0u; @@ -735,7 +696,7 @@ void layout_dyn(Linker* l, LinkImage* img) { LinkDynState* dyn; LinkDynState dyn_probe; ImportLists imports; - ByteBuf dynstr; + ObjByteBuf dynstr; u64 page; const LinkArchDesc* arch; const ObjElfArchOps* elf_arch; @@ -799,7 +760,7 @@ void layout_dyn(Linker* l, LinkImage* img) { * DT_NEEDED soname strings the .dynamic body references; intern * them after the import names so build_dynsym's de-dup also covers * any name that happens to collide with a soname. */ - bb_init(&dynstr, h); + objbb_init(&dynstr, h); build_dynsym(img, dyn, &imports, &dynstr); { u32 ni; @@ -807,7 +768,7 @@ void layout_dyn(Linker* l, LinkImage* img) { Slice s_s = pool_slice(l->c->global, dyn->needed[ni]); const char* s = s_s.s; size_t slen = s_s.len; - if (s && slen) (void)bb_append_str(&dynstr, s, (u32)slen); + if (s && slen) (void)objbb_append_str(&dynstr, s, (u32)slen); } } /* Symbol versioning: assign per-import version requirements and append the diff --git a/src/obj/elf/link_dyn.h b/src/obj/elf/link_dyn.h @@ -0,0 +1,143 @@ +#ifndef KIT_OBJ_ELF_LINK_DYN_H +#define KIT_OBJ_ELF_LINK_DYN_H + +/* ELF dynamic-link synthesis state. + * + * DynSymRec / DynRela are ELF64 wire-format records and LinkDynState is the + * ELF linker's working state for .dynsym / .dynstr / .gnu.hash / .rela.* / + * .plt / .got.plt / .dynamic. They live here, in an ELF-only header, so the + * format-neutral link_internal.h can carry an opaque `LinkDynState* dyn` on + * LinkImage without leaking ELF field names into the COFF/Mach-O linkers. + * Only src/obj/elf/link.c and src/obj/elf/link_dyn.c include this. */ + +#include "core/core.h" +#include "link/link.h" + +/* ---- Dynamic-link synthesis state (Phase 4) ---- + * + * Owned by LinkImage when emit_pie is set. Holds the synthesized + * .interp / .dynsym / .dynstr / .gnu.hash / .rela.dyn / .rela.plt / + * .plt / .got.plt / .dynamic content plus the section ids the emit + * pass needs to fill PT_DYNAMIC and the .dynamic body. + * + * Phase 4 builds the dynsym/dynstr/gnu.hash content and the JUMP_SLOT + * .rela.plt records (one per imported function, against its synthetic + * .got.plt slot). The .plt body is allocated but not emitted (Phase 5). + * Phase 6 populates .rela.dyn with R_AARCH64_RELATIVE records for any + * internal absolute reloc seen during reloc-apply. + * + * Layout invariants this struct enforces: + * - dynsym entry 0 is the reserved STN_UNDEF slot (zero-filled). + * - dynsym entries 1..nimport_func+nimport_data are imports, in the + * order PLT-functions first, then GOT-data. + * - PLT slots and JUMP_SLOT entries match the import_func order 1:1. + * - .got.plt has 3 reserved leading u64 slots (per AArch64 psABI: + * slot 0 = &.dynamic, slot 1 = link_map cookie, slot 2 = + * _dl_runtime_resolve), then one slot per imported function. + */ + +typedef struct DynSymRec { + u32 st_name; /* offset into .dynstr */ + u8 st_info; + u8 st_other; + u16 st_shndx; + u64 st_value; + u64 st_size; +} DynSymRec; + +typedef struct DynRela { + u64 r_offset; /* image-relative vaddr of the patch site */ + u64 r_info; /* ELF64_R_INFO(dynsym_index, elf_reloc_type) */ + i64 r_addend; +} DynRela; + +typedef struct LinkDynState { + /* PT_INTERP / .interp. interp_path is interned in compiler->global. */ + Sym interp_path; + LinkSectionId sec_interp; + + /* .dynsym */ + LinkSectionId sec_dynsym; + DynSymRec* dynsym; + u32 ndynsym; /* incl. slot-0 STN_UNDEF */ + u32 first_global; /* sh_info value: index of first non-local entry */ + + /* .dynstr */ + LinkSectionId sec_dynstr; + u8* dynstr; + u32 dynstr_len; + + /* .gnu.hash */ + LinkSectionId sec_gnu_hash; + u8* gnu_hash; + u32 gnu_hash_len; + + /* GNU symbol versioning. Emitted only when at least one imported symbol + * binds to a versioned DSO export (nverneed > 0); otherwise all three are + * zero and no version sections / DT_VER* entries are produced (musl/static + * links are unchanged). .gnu.version is one u16 per .dynsym entry; + * .gnu.version_r holds Verneed/Vernaux requirements keyed by DT_NEEDED + * soname. Both carry only .dynstr offsets + version indices (no vaddrs), so + * the bytes are final at layout time and copied verbatim during emit. */ + LinkSectionId sec_gnu_version; + u8* versym; /* ndynsym * 2 bytes */ + u32 versym_len; + LinkSectionId sec_gnu_version_r; + u8* verneed; /* nverneed Verneed records + their Vernaux */ + u32 verneed_len; + u32 nverneed; /* DT_VERNEEDNUM */ + + /* .rela.dyn — R_AARCH64_GLOB_DAT (imports against .got slots) and + * R_AARCH64_RELATIVE (PIE internal abs64 fixups, populated during + * Phase 6 emit). Pre-sized at layout time; the RELATIVE tail is + * filled in during emit. */ + LinkSectionId sec_rela_dyn; + DynRela* rela_dyn; + u32 nrela_dyn; /* number of records currently populated */ + u32 cap_rela_dyn; /* allocation capacity (records, not bytes) */ + + /* .rela.plt — R_AARCH64_JUMP_SLOT, one per imported function. */ + LinkSectionId sec_rela_plt; + DynRela* rela_plt; + u32 nrela_plt; + + /* .plt — 32-byte PLT0 stub + 16 bytes per imported function. Body + * is allocated (zero-initialized) but not emitted in Phase 4. */ + LinkSectionId sec_plt; + u32 nplt; /* number of imported functions */ + u64 plt_vaddr; /* image-relative .plt base */ + u64 plt_size; + + /* .got.plt — 24 reserved bytes + 8 per PLT slot. */ + LinkSectionId sec_got_plt; + u64 got_plt_vaddr; + u64 got_plt_size; + + /* .dynamic — PT_DYNAMIC body. Built at layout time; its size is + * fixed once we know the DT_NEEDED count. */ + LinkSectionId sec_dynamic; + u64 dynamic_vaddr; + u64 dynamic_size; + u32 ndyn_entries; + + /* DT_NEEDED list (interned soname Syms, in input order). */ + Sym* needed; + u32 nneeded; + + /* Per-import dynsym index, indexed by LinkSymId. 0 means "not + * imported / not in dynsym". Used by GLOB_DAT / JUMP_SLOT emit. */ + u32* sym_dynidx; /* size = sym_dynidx_size */ + u32 sym_dynidx_size; + + /* Per-import PLT entry vaddr, indexed by LinkSymId (Phase 5). Set + * for every imported function: vaddr of its 16-byte PLT stub inside + * `.plt`. 0 means "no PLT stub" (symbol is data-only or not + * imported). apply_all_relocs reads this when redirecting a + * CALL26/JUMP26 against an imported function — S becomes the PLT + * entry vaddr instead of the symbol's (zero) vaddr. The vaddrs + * stored here track the post-shift values (shift_image_addresses + * bumps them along with .plt's segment vaddr). */ + u64* sym_plt_vaddr; /* size = sym_dynidx_size */ +} LinkDynState; + +#endif diff --git a/src/obj/macho/link.c b/src/obj/macho/link.c @@ -51,6 +51,7 @@ #include "link/link_arch.h" #include "link/link_internal.h" #include "link/link_reloc_desc.h" +#include "obj/bytebuf.h" #include "obj/format.h" #include "obj/macho/macho.h" @@ -84,70 +85,9 @@ #define LC_DATA_IN_CODE_C 0x29u #define LC_CODE_SIGNATURE_C 0x1du -/* ---- byte buffer ---- */ - -typedef struct MByte { - Heap* heap; - u8* data; - u32 len; - u32 cap; -} MByte; - -static void mbuf_init(MByte* b, Heap* h) { - b->heap = h; - b->data = NULL; - b->len = 0; - b->cap = 0; -} -static void mbuf_fini(MByte* b) { - if (b->data) b->heap->free(b->heap, b->data, b->cap); - b->data = NULL; - b->cap = b->len = 0; -} -static void mbuf_reserve(MByte* b, u32 need) { - if (need <= b->cap) return; - (void)VEC_GROW(b->heap, b->data, b->cap, need); -} -static u32 mbuf_align(MByte* b, u32 a) { - u32 n = (u32)ALIGN_UP((u64)b->len, (u64)a); - if (n > b->len) { - mbuf_reserve(b, n); - memset(b->data + b->len, 0, n - b->len); - b->len = n; - } - return b->len; -} -static u32 mbuf_append(MByte* b, const void* src, u32 n) { - u32 off = b->len; - mbuf_reserve(b, b->len + n); - if (n) memcpy(b->data + b->len, src, n); - b->len += n; - return off; -} -static u32 mbuf_u32(MByte* b, u32 v) { - u8 t[4]; - wr_u32_le(t, v); - return mbuf_append(b, t, 4); -} -static u32 mbuf_u16(MByte* b, u16 v) { - u8 t[2]; - wr_u16_le(t, v); - return mbuf_append(b, t, 2); -} -static u32 mbuf_u64(MByte* b, u64 v) { - u8 t[8]; - wr_u64_le(t, v); - return mbuf_append(b, t, 8); -} -static u32 mbuf_u8(MByte* b, u8 v) { return mbuf_append(b, &v, 1); } -static u32 mbuf_str(MByte* b, const char* s, u32 n) { - u32 off = b->len; - mbuf_reserve(b, b->len + n + 1u); - if (n) memcpy(b->data + b->len, s, n); - b->data[b->len + n] = 0; - b->len += n + 1u; - return off; -} +/* ---- byte buffer ---- + * Linkedit tables / load commands / the output file are staged in the shared + * ObjByteBuf (objbb_*). */ /* ---- imports + dylibs ---- */ @@ -348,14 +288,14 @@ typedef struct MCtx { u64 headers_size; /* header + loadcmds */ /* LINKEDIT contents */ - MByte chained_fixups; - MByte exports_trie; - MByte symtab; /* binary nlist_64 array */ - MByte strtab; - MByte indirect; /* u32 array */ - MByte fn_starts; - MByte data_in_code; - MByte codesig; + ObjByteBuf chained_fixups; + ObjByteBuf exports_trie; + ObjByteBuf symtab; /* binary nlist_64 array */ + ObjByteBuf strtab; + ObjByteBuf indirect; /* u32 array */ + ObjByteBuf fn_starts; + ObjByteBuf data_in_code; + ObjByteBuf codesig; u32 chained_fixups_off; u32 exports_trie_off; @@ -1681,8 +1621,8 @@ static void emit_pointer(u8* slot, int is_bind, u32 ord_or_target_lo, static void build_chained_fixups(MCtx* x, FixList* fl) { Heap* h = x->h; - MByte* out = &x->chained_fixups; - mbuf_init(out, h); + ObjByteBuf* out = &x->chained_fixups; + objbb_init(out, h); /* Header (32 B): * uint32 fixups_version (=0) @@ -1693,16 +1633,16 @@ static void build_chained_fixups(MCtx* x, FixList* fl) { * uint32 imports_format (=1) * uint32 symbols_format (=0) */ - u32 hdr_pos = mbuf_u32(out, 0); /* fixups_version */ + u32 hdr_pos = objbb_u32(out, 0); /* fixups_version */ (void)hdr_pos; - u32 starts_offset_pos = mbuf_u32(out, 0); - u32 imports_offset_pos = mbuf_u32(out, 0); - u32 symbols_offset_pos = mbuf_u32(out, 0); - mbuf_u32(out, x->nimports_real); - mbuf_u32(out, DYLD_CHAINED_IMPORT); - mbuf_u32(out, 0); /* symbols uncompressed */ + u32 starts_offset_pos = objbb_u32(out, 0); + u32 imports_offset_pos = objbb_u32(out, 0); + u32 symbols_offset_pos = objbb_u32(out, 0); + objbb_u32(out, x->nimports_real); + objbb_u32(out, DYLD_CHAINED_IMPORT); + objbb_u32(out, 0); /* symbols uncompressed */ /* dyld expects 8-byte alignment of the starts table. */ - mbuf_align(out, 4); + objbb_align(out, 4); /* dyld_chained_starts_in_image: * uint32 seg_count @@ -1713,10 +1653,10 @@ static void build_chained_fixups(MCtx* x, FixList* fl) { */ u32 starts_off = out->len; wr_u32_le(out->data + starts_offset_pos, starts_off); - mbuf_u32(out, x->nsegs); + objbb_u32(out, x->nsegs); /* Reserve seg_info_offset[]. */ u32 seg_info_offsets_pos = out->len; - for (u32 i = 0; i < x->nsegs; ++i) mbuf_u32(out, 0); + for (u32 i = 0; i < x->nsegs; ++i) objbb_u32(out, 0); /* Sort fixsites by vaddr globally. */ sort_sites(fl->a, fl->n); @@ -1733,7 +1673,7 @@ static void build_chained_fixups(MCtx* x, FixList* fl) { } if (!count) continue; /* Page-align this struct to 4. */ - mbuf_align(out, 4); + objbb_align(out, 4); u32 sis_off = out->len; /* Patch seg_info_offset[si] to (sis_off - starts_off). */ wr_u32_le(out->data + seg_info_offsets_pos + si * 4u, sis_off - starts_off); @@ -1753,14 +1693,14 @@ static void build_chained_fixups(MCtx* x, FixList* fl) { * uint16 page_count * uint16 page_start[page_count] (0xFFFF = no fixups in page) */ - u32 sis_size_pos = mbuf_u32(out, 0); /* fill below */ - mbuf_u16(out, (u16)MZ_PAGE); - mbuf_u16(out, (u16)DYLD_CHAINED_PTR_64); - mbuf_u64(out, (u64)x->segs[si].fileoff); /* segment file offset */ - mbuf_u32(out, 0); - mbuf_u16(out, (u16)page_count); + u32 sis_size_pos = objbb_u32(out, 0); /* fill below */ + objbb_u16(out, (u16)MZ_PAGE); + objbb_u16(out, (u16)DYLD_CHAINED_PTR_64); + objbb_u64(out, (u64)x->segs[si].fileoff); /* segment file offset */ + objbb_u32(out, 0); + objbb_u16(out, (u16)page_count); u32 page_starts_pos = out->len; - for (u32 p = 0; p < page_count; ++p) mbuf_u16(out, 0xFFFFu); + for (u32 p = 0; p < page_count; ++p) objbb_u16(out, 0xFFFFu); /* size includes the page_start array */ u32 sis_size = out->len - sis_size_pos + 4u; /* Hmm, the `size` field is the size of *this* struct. We measure @@ -1847,17 +1787,17 @@ static void build_chained_fixups(MCtx* x, FixList* fl) { /* Imports table: one dyld_chained_import (4B) per real import. * Layout: lib_ordinal:8, weak:1, name_offset:23. Internal-GOT * entries are not bound by dyld so they're omitted here. */ - mbuf_align(out, 4); + objbb_align(out, 4); u32 imports_off = out->len; wr_u32_le(out->data + imports_offset_pos, imports_off); /* We need to first build the symbol pool to know name offsets. */ u32 symbols_off = imports_off + x->nimports_real * 4u; /* Reserve imports area. */ - for (u32 i = 0; i < x->nimports_real; ++i) mbuf_u32(out, 0); + for (u32 i = 0; i < x->nimports_real; ++i) objbb_u32(out, 0); /* Emit symbols (each NUL-terminated). Set name_offset on each import. */ wr_u32_le(out->data + symbols_offset_pos, out->len); /* Leading NUL for offset 0. */ - mbuf_u8(out, 0); + objbb_u8(out, 0); for (u32 i = 0; i < x->nimports_real; ++i) { MachImp* mi = &x->imports[i]; Slice nm_s = pool_slice(x->c->global, mi->name); @@ -1871,7 +1811,7 @@ static void build_chained_fixups(MCtx* x, FixList* fl) { (unsigned)x->ndylibs); } u32 off = out->len - symbols_off; - mbuf_str(out, nm, (u32)nl); + objbb_str(out, nm, (u32)nl); /* Patch the import slot. */ u32 packed = ((u32)mi->dylib_ord & 0xffu) | ((u32)(mi->weak ? 1u : 0u) << 8) | ((off & 0x7fffffu) << 9); @@ -1886,12 +1826,12 @@ static void build_chained_fixups(MCtx* x, FixList* fl) { * entry symbol's VA-relative offset. This is enough for dyld; binaries * with a real exports trie include more data but we don't need it. */ -static void uleb128(MByte* out, u64 v) { +static void uleb128(ObjByteBuf* out, u64 v) { do { u8 byte = v & 0x7fu; v >>= 7; if (v) byte |= 0x80u; - mbuf_u8(out, byte); + objbb_u8(out, byte); } while (v); } @@ -1915,23 +1855,23 @@ static void build_exports_trie(MCtx* x) { * Easiest: single root node with children_count=1, child label = "_main", * child offset points to a leaf node. */ - MByte* out = &x->exports_trie; - mbuf_init(out, x->h); + ObjByteBuf* out = &x->exports_trie; + objbb_init(out, x->h); LinkImage* img = x->img; LinkSymbol* esym = sym_at(img, img->entry_sym); if (!esym || !esym->defined) { /* No entry — emit a single empty terminal trie. */ - mbuf_u8(out, 0); /* terminal_size 0 */ - mbuf_u8(out, 0); /* children 0 */ + objbb_u8(out, 0); /* terminal_size 0 */ + objbb_u8(out, 0); /* children 0 */ return; } Slice nm_s = pool_slice(x->c->global, esym->name); const char* nm = nm_s.s; size_t nl = nm_s.len; if (!nm || nl == 0) { - mbuf_u8(out, 0); - mbuf_u8(out, 0); + objbb_u8(out, 0); + objbb_u8(out, 0); return; } /* leaf node: terminal_size = sizeof(uleb(flags)+uleb(offset)) @@ -1957,21 +1897,21 @@ static void build_exports_trie(MCtx* x) { leaf_pos = next; } - mbuf_u8(out, 0); /* root terminal size */ - mbuf_u8(out, 1); /* children_count */ - mbuf_str(out, nm, (u32)nl); + objbb_u8(out, 0); /* root terminal size */ + objbb_u8(out, 1); /* children_count */ + objbb_str(out, nm, (u32)nl); uleb128(out, leaf_pos); /* leaf node */ if (out->len != leaf_pos) compiler_panic(x->c, SRCLOC_NONE, "macho: exports trie leaf offset mismatch"); /* terminal_size byte then payload */ - mbuf_u8(out, (u8)leaf_payload_len); + objbb_u8(out, (u8)leaf_payload_len); uleb128(out, flags); uleb128(out, entry_off); - mbuf_u8(out, 0); /* children_count */ + objbb_u8(out, 0); /* children_count */ /* Pad trie to 8 bytes. */ - mbuf_align(out, 8); + objbb_align(out, 8); } /* ---- symtab + strtab + indirect symtab ---- */ @@ -1987,12 +1927,12 @@ typedef struct NlistRec { static void build_symtab(MCtx* x) { Heap* h = x->h; LinkImage* img = x->img; - mbuf_init(&x->symtab, h); - mbuf_init(&x->strtab, h); - mbuf_init(&x->indirect, h); + objbb_init(&x->symtab, h); + objbb_init(&x->strtab, h); + objbb_init(&x->indirect, h); /* strtab leading NUL */ - mbuf_u8(&x->strtab, 0); + objbb_u8(&x->strtab, 0); /* Approach: * - Add one local nlist per defined LinkSymbol (locals + non-imported @@ -2049,7 +1989,7 @@ static void build_symtab(MCtx* x) { const char* nm = nm_s.s; size_t nl = nm_s.len; u32 strx = x->strtab.len; - if (nm && nl) mbuf_str(&x->strtab, nm, (u32)nl); + if (nm && nl) objbb_str(&x->strtab, nm, (u32)nl); u8 t[16]; u8 nt = N_SECT | N_EXT; @@ -2061,7 +2001,7 @@ static void build_symtab(MCtx* x) { t[5] = n_sect; wr_u16_le(t + 6, s->bind == SB_WEAK ? N_WEAK_DEF : 0); wr_u64_le(t + 8, s->vaddr); - mbuf_append(&x->symtab, t, 16); + objbb_append(&x->symtab, t, 16); ++n_extdef; } @@ -2074,7 +2014,7 @@ static void build_symtab(MCtx* x) { const char* nm = nm_s.s; size_t nl = nm_s.len; u32 strx = x->strtab.len; - if (nm && nl) mbuf_str(&x->strtab, nm, (u32)nl); + if (nm && nl) objbb_str(&x->strtab, nm, (u32)nl); u8 t[16]; wr_u32_le(t + 0, strx); @@ -2086,7 +2026,7 @@ static void build_symtab(MCtx* x) { if (mi->weak) desc |= N_WEAK_REF; wr_u16_le(t + 6, desc); wr_u64_le(t + 8, 0); - mbuf_append(&x->symtab, t, 16); + objbb_append(&x->symtab, t, 16); ++n_undef; } @@ -2105,7 +2045,7 @@ static void build_symtab(MCtx* x) { MachImp* mi = &x->imports[k]; if (!mi->stub_idx) continue; u32 sym_idx = imp_first_symtab_idx + k; - mbuf_u32(&x->indirect, sym_idx); + objbb_u32(&x->indirect, sym_idx); ++indirect_start; } } @@ -2118,7 +2058,7 @@ static void build_symtab(MCtx* x) { MachImp* mi = &x->imports[k]; u32 sym_idx = mi->internal ? 0x80000000u /* INDIRECT_SYMBOL_LOCAL */ : (imp_first_symtab_idx + k); - mbuf_u32(&x->indirect, sym_idx); + objbb_u32(&x->indirect, sym_idx); ++indirect_start; } } @@ -2140,10 +2080,10 @@ static void layout_linkedit(MCtx* x) { /* LC_FUNCTION_STARTS is a ULEB128 stream terminated by a zero byte. Keep a * real empty table here so tools that rewrite LINKEDIT preserve the * canonical blob order between exports and the symbol table. */ - mbuf_init(&x->fn_starts, x->h); - mbuf_u8(&x->fn_starts, 0); - mbuf_init(&x->data_in_code, x->h); - mbuf_init(&x->codesig, x->h); + objbb_init(&x->fn_starts, x->h); + objbb_u8(&x->fn_starts, 0); + objbb_init(&x->data_in_code, x->h); + objbb_init(&x->codesig, x->h); u64 cur = x->linkedit_fileoff; /* chained fixups */ @@ -2242,9 +2182,9 @@ static void build_codesig_skeleton(MCtx* x, u32 code_limit, const char* ident) { /* SuperBlob: 12 hdr + 8 slot + cd. */ u32 sb_size = 12u + 8u + cd_size; - MByte* out = &x->codesig; - mbuf_init(out, x->h); - mbuf_reserve(out, sb_size); + ObjByteBuf* out = &x->codesig; + objbb_init(out, x->h); + objbb_reserve(out, sb_size); memset(out->data, 0, sb_size); out->len = sb_size; @@ -2311,27 +2251,27 @@ static void compute_codesig(MCtx* x, const u8* full_file, u32 file_len_excl_cs, /* ---- final emission ---- */ -static void emit_load_command_segment(MByte* lc, MCtx* x, u32 segidx) { +static void emit_load_command_segment(ObjByteBuf* lc, MCtx* x, u32 segidx) { MSeg* sg = &x->segs[segidx]; u32 seg_cmd_size = MACHO_SEGCMD64_SIZE + sg->nouts * MACHO_SECT64_SIZE; u32 base = lc->len; - mbuf_u32(lc, LC_SEGMENT_64); - mbuf_u32(lc, seg_cmd_size); + objbb_u32(lc, LC_SEGMENT_64); + objbb_u32(lc, seg_cmd_size); /* segname: 16 bytes zero-padded */ u8 nm[16]; memset(nm, 0, 16); size_t nlen = slice_from_cstr(sg->name).len; if (nlen > 16) nlen = 16; memcpy(nm, sg->name, nlen); - mbuf_append(lc, nm, 16); - mbuf_u64(lc, sg->vmaddr); - mbuf_u64(lc, sg->vmsize); - mbuf_u64(lc, sg->fileoff); - mbuf_u64(lc, sg->filesize); - mbuf_u32(lc, sg->maxprot); - mbuf_u32(lc, sg->initprot); - mbuf_u32(lc, sg->nouts); - mbuf_u32(lc, 0); /* flags */ + objbb_append(lc, nm, 16); + objbb_u64(lc, sg->vmaddr); + objbb_u64(lc, sg->vmsize); + objbb_u64(lc, sg->fileoff); + objbb_u64(lc, sg->filesize); + objbb_u32(lc, sg->maxprot); + objbb_u32(lc, sg->initprot); + objbb_u32(lc, sg->nouts); + objbb_u32(lc, 0); /* flags */ for (u32 j = 0; j < sg->nouts; ++j) { OutSec* o = &x->outs[sg->first_out + j]; @@ -2344,22 +2284,22 @@ static void emit_load_command_segment(MByte* lc, MCtx* x, u32 segidx) { size_t gl = slice_from_cstr(sg->name).len; /* segname must match */ if (gl > 16) gl = 16; memcpy(gname, sg->name, gl); - mbuf_append(lc, sname, 16); - mbuf_append(lc, gname, 16); - mbuf_u64(lc, o->vaddr); - mbuf_u64(lc, o->size); - mbuf_u32(lc, (u32)o->file_offset); + objbb_append(lc, sname, 16); + objbb_append(lc, gname, 16); + objbb_u64(lc, o->vaddr); + objbb_u64(lc, o->size); + objbb_u32(lc, (u32)o->file_offset); /* align is power of 2; encode as log2. */ u32 a = o->align ? o->align : 1u; u32 al = 0; while ((1u << al) < a) ++al; - mbuf_u32(lc, al); - mbuf_u32(lc, 0); /* reloff */ - mbuf_u32(lc, 0); /* nreloc */ - mbuf_u32(lc, o->flags); - mbuf_u32(lc, o->reserved1); - mbuf_u32(lc, o->reserved2); - mbuf_u32(lc, 0); /* reserved3 */ + objbb_u32(lc, al); + objbb_u32(lc, 0); /* reloff */ + objbb_u32(lc, 0); /* nreloc */ + objbb_u32(lc, o->flags); + objbb_u32(lc, o->reserved1); + objbb_u32(lc, o->reserved2); + objbb_u32(lc, 0); /* reserved3 */ } (void)base; } @@ -2427,8 +2367,8 @@ void link_emit_macho(LinkImage* img, Writer* w) { x.segs[MSEG_LINKEDIT].vmsize = ALIGN_UP(le_size, MZ_PAGE); /* Build load commands buffer. */ - MByte lc; - mbuf_init(&lc, x.h); + ObjByteBuf lc; + objbb_init(&lc, x.h); /* LC_SEGMENT_64 for each segment with sections (and PAGEZERO/LINKEDIT). */ emit_load_command_segment(&lc, &x, 0); /* PAGEZERO */ @@ -2441,24 +2381,24 @@ void link_emit_macho(LinkImage* img, Writer* w) { emit_load_command_segment(&lc, &x, MSEG_LINKEDIT); /* LINKEDIT */ /* LC_DYLD_CHAINED_FIXUPS (linkedit_data_command: 16B) */ - mbuf_u32(&lc, LC_DYLD_CHAINED_FIXUPS); - mbuf_u32(&lc, 16); - mbuf_u32(&lc, x.chained_fixups_off); - mbuf_u32(&lc, x.chained_fixups.len); + objbb_u32(&lc, LC_DYLD_CHAINED_FIXUPS); + objbb_u32(&lc, 16); + objbb_u32(&lc, x.chained_fixups_off); + objbb_u32(&lc, x.chained_fixups.len); /* LC_DYLD_EXPORTS_TRIE */ - mbuf_u32(&lc, LC_DYLD_EXPORTS_TRIE); - mbuf_u32(&lc, 16); - mbuf_u32(&lc, x.exports_trie_off); - mbuf_u32(&lc, x.exports_trie.len); + objbb_u32(&lc, LC_DYLD_EXPORTS_TRIE); + objbb_u32(&lc, 16); + objbb_u32(&lc, x.exports_trie_off); + objbb_u32(&lc, x.exports_trie.len); /* LC_SYMTAB */ - mbuf_u32(&lc, LC_SYMTAB); - mbuf_u32(&lc, MACHO_SYMTAB_CMD_SIZE); - mbuf_u32(&lc, x.symtab_off); - mbuf_u32(&lc, x.nsyms); - mbuf_u32(&lc, x.strtab_off); - mbuf_u32(&lc, x.strtab.len); + objbb_u32(&lc, LC_SYMTAB); + objbb_u32(&lc, MACHO_SYMTAB_CMD_SIZE); + objbb_u32(&lc, x.symtab_off); + objbb_u32(&lc, x.nsyms); + objbb_u32(&lc, x.strtab_off); + objbb_u32(&lc, x.strtab.len); /* LC_DYSYMTAB */ /* nlocal=0, nextdef=#defined-globals, nundef=#imports. We tracked @@ -2467,39 +2407,39 @@ void link_emit_macho(LinkImage* img, Writer* w) { u32 nlocal = 0; u32 nundef = x.nimports_real; u32 nextdef = (x.nsyms > nundef) ? x.nsyms - nundef - nlocal : 0; - mbuf_u32(&lc, LC_DYSYMTAB); - mbuf_u32(&lc, MACHO_DYSYMTAB_CMD_SIZE); - mbuf_u32(&lc, 0); /* ilocalsym */ - mbuf_u32(&lc, nlocal); - mbuf_u32(&lc, nlocal); - mbuf_u32(&lc, nextdef); - mbuf_u32(&lc, nlocal + nextdef); - mbuf_u32(&lc, nundef); - mbuf_u32(&lc, 0); - mbuf_u32(&lc, 0); /* tocoff, ntoc */ - mbuf_u32(&lc, 0); - mbuf_u32(&lc, 0); /* modtaboff, nmodtab */ - mbuf_u32(&lc, 0); - mbuf_u32(&lc, 0); /* extrefsymoff, nextrefsyms */ - mbuf_u32(&lc, x.indirect_off); - mbuf_u32(&lc, x.indirect.len / 4u); - mbuf_u32(&lc, 0); - mbuf_u32(&lc, 0); /* extreloff, nextrel */ - mbuf_u32(&lc, 0); - mbuf_u32(&lc, 0); /* locreloff, nlocrel */ + objbb_u32(&lc, LC_DYSYMTAB); + objbb_u32(&lc, MACHO_DYSYMTAB_CMD_SIZE); + objbb_u32(&lc, 0); /* ilocalsym */ + objbb_u32(&lc, nlocal); + objbb_u32(&lc, nlocal); + objbb_u32(&lc, nextdef); + objbb_u32(&lc, nlocal + nextdef); + objbb_u32(&lc, nundef); + objbb_u32(&lc, 0); + objbb_u32(&lc, 0); /* tocoff, ntoc */ + objbb_u32(&lc, 0); + objbb_u32(&lc, 0); /* modtaboff, nmodtab */ + objbb_u32(&lc, 0); + objbb_u32(&lc, 0); /* extrefsymoff, nextrefsyms */ + objbb_u32(&lc, x.indirect_off); + objbb_u32(&lc, x.indirect.len / 4u); + objbb_u32(&lc, 0); + objbb_u32(&lc, 0); /* extreloff, nextrel */ + objbb_u32(&lc, 0); + objbb_u32(&lc, 0); /* locreloff, nlocrel */ /* LC_LOAD_DYLINKER */ { const char* dyld = "/usr/lib/dyld"; u32 dyld_len = (u32)slice_from_cstr(dyld).len; u32 cmd_size = (u32)ALIGN_UP((u64)(12u + dyld_len + 1u), 8u); - mbuf_u32(&lc, LC_LOAD_DYLINKER); - mbuf_u32(&lc, cmd_size); - mbuf_u32(&lc, 12u); /* name offset within cmd */ - u32 wrote = mbuf_str(&lc, dyld, dyld_len); + objbb_u32(&lc, LC_LOAD_DYLINKER); + objbb_u32(&lc, cmd_size); + objbb_u32(&lc, 12u); /* name offset within cmd */ + u32 wrote = objbb_str(&lc, dyld, dyld_len); (void)wrote; /* Pad to cmd_size. */ - while (lc.len < (u32)((u64)mbuf_align(&lc, 1) + 0)) { + while (lc.len < (u32)((u64)objbb_align(&lc, 1) + 0)) { /* no-op */ break; } @@ -2508,29 +2448,29 @@ void link_emit_macho(LinkImage* img, Writer* w) { /* Walk back: lc grew by 12 + (strlen+1). Pad to cmd_size. */ u32 cmd_start_back = lc.len - (12u + dyld_len + 1u); u32 pad_needed = cmd_size - (lc.len - cmd_start_back); - while (pad_needed-- > 0) mbuf_u8(&lc, 0); + while (pad_needed-- > 0) objbb_u8(&lc, 0); (void)want; } /* LC_UUID */ - mbuf_u32(&lc, LC_UUID); - mbuf_u32(&lc, 24); - mbuf_append(&lc, x.uuid, 16); + objbb_u32(&lc, LC_UUID); + objbb_u32(&lc, 24); + objbb_append(&lc, x.uuid, 16); /* LC_BUILD_VERSION */ - mbuf_u32(&lc, LC_BUILD_VERSION); - mbuf_u32(&lc, 24); - mbuf_u32(&lc, 1); /* PLATFORM_MACOS */ - mbuf_u32(&lc, (12u << 16) | 0); /* minos 12.0.0 */ - mbuf_u32(&lc, (12u << 16) | 0); /* sdk 12.0.0 */ - mbuf_u32(&lc, 0); /* ntools */ + objbb_u32(&lc, LC_BUILD_VERSION); + objbb_u32(&lc, 24); + objbb_u32(&lc, 1); /* PLATFORM_MACOS */ + objbb_u32(&lc, (12u << 16) | 0); /* minos 12.0.0 */ + objbb_u32(&lc, (12u << 16) | 0); /* sdk 12.0.0 */ + objbb_u32(&lc, 0); /* ntools */ /* LC_MAIN — entryoff is offset within __TEXT segment from its file * start (0). */ - mbuf_u32(&lc, LC_MAIN); - mbuf_u32(&lc, 24); - mbuf_u64(&lc, (u64)x.entry_offset); /* entryoff = vaddr - __TEXT.vmaddr */ - mbuf_u64(&lc, 0); /* stacksize */ + objbb_u32(&lc, LC_MAIN); + objbb_u32(&lc, 24); + objbb_u64(&lc, (u64)x.entry_offset); /* entryoff = vaddr - __TEXT.vmaddr */ + objbb_u64(&lc, 0); /* stacksize */ /* LC_LOAD_DYLIB per dylib. */ for (u32 i = 0; i < x.ndylibs; ++i) { @@ -2539,32 +2479,32 @@ void link_emit_macho(LinkImage* img, Writer* w) { size_t nl = nm_s.len; u32 cmd_size = (u32)ALIGN_UP((u64)(24u + (u32)nl + 1u), 8u); u32 cmd_start = lc.len; - mbuf_u32(&lc, LC_LOAD_DYLIB); - mbuf_u32(&lc, cmd_size); - mbuf_u32(&lc, 24u); /* name offset */ - mbuf_u32(&lc, 0); /* timestamp */ - mbuf_u32(&lc, (1u << 16)); /* current_version 1.0 */ - mbuf_u32(&lc, (1u << 16)); /* compat_version 1.0 */ - mbuf_str(&lc, nm ? nm : "", (u32)nl); - while (lc.len - cmd_start < cmd_size) mbuf_u8(&lc, 0); + objbb_u32(&lc, LC_LOAD_DYLIB); + objbb_u32(&lc, cmd_size); + objbb_u32(&lc, 24u); /* name offset */ + objbb_u32(&lc, 0); /* timestamp */ + objbb_u32(&lc, (1u << 16)); /* current_version 1.0 */ + objbb_u32(&lc, (1u << 16)); /* compat_version 1.0 */ + objbb_str(&lc, nm ? nm : "", (u32)nl); + while (lc.len - cmd_start < cmd_size) objbb_u8(&lc, 0); } /* LC_FUNCTION_STARTS / LC_DATA_IN_CODE */ - mbuf_u32(&lc, LC_FUNCTION_STARTS_C); - mbuf_u32(&lc, 16); - mbuf_u32(&lc, x.fn_starts_off); - mbuf_u32(&lc, x.fn_starts.len); + objbb_u32(&lc, LC_FUNCTION_STARTS_C); + objbb_u32(&lc, 16); + objbb_u32(&lc, x.fn_starts_off); + objbb_u32(&lc, x.fn_starts.len); - mbuf_u32(&lc, LC_DATA_IN_CODE_C); - mbuf_u32(&lc, 16); - mbuf_u32(&lc, x.data_in_code_off); - mbuf_u32(&lc, 0); + objbb_u32(&lc, LC_DATA_IN_CODE_C); + objbb_u32(&lc, 16); + objbb_u32(&lc, x.data_in_code_off); + objbb_u32(&lc, 0); /* LC_CODE_SIGNATURE */ - mbuf_u32(&lc, LC_CODE_SIGNATURE_C); - mbuf_u32(&lc, 16); - mbuf_u32(&lc, x.codesig_off); - mbuf_u32(&lc, x.codesig_size); + objbb_u32(&lc, LC_CODE_SIGNATURE_C); + objbb_u32(&lc, 16); + objbb_u32(&lc, x.codesig_off); + objbb_u32(&lc, x.codesig_size); /* Sanity: lc.len + MACHO_HDR64_SIZE must equal headers_size we * predicted in plan_layout. If not, we mis-sized — panic. */ @@ -2578,8 +2518,8 @@ void link_emit_macho(LinkImage* img, Writer* w) { /* ---- now stream the file ---- */ /* The Writer in kit allows seek; we'll write a flat buffer first * (so we can hash it for codesig) and flush at the end. */ - MByte file; - mbuf_init(&file, x.h); + ObjByteBuf file; + objbb_init(&file, x.h); /* mach_header_64 */ u32 ncmds = 0; @@ -2596,12 +2536,12 @@ void link_emit_macho(LinkImage* img, Writer* w) { /* (chained, exports_trie, symtab, dysymtab, dyld, uuid, build_version, * main, fn_starts, data_in_code, codesig) = 11 */ - mbuf_u32(&file, MH_MAGIC_64); - mbuf_u32(&file, x.macho->cputype); - mbuf_u32(&file, x.macho->cpusubtype); - mbuf_u32(&file, MH_EXECUTE); - mbuf_u32(&file, ncmds); - mbuf_u32(&file, lc.len); + objbb_u32(&file, MH_MAGIC_64); + objbb_u32(&file, x.macho->cputype); + objbb_u32(&file, x.macho->cpusubtype); + objbb_u32(&file, MH_EXECUTE); + objbb_u32(&file, ncmds); + objbb_u32(&file, lc.len); { u32 mh_flags = MH_DYLDLINK | MH_TWOLEVEL | MH_NOUNDEFS | MH_PIE; /* dyld scans __thread_vars and allocates a pthread_key for each @@ -2609,10 +2549,10 @@ void link_emit_macho(LinkImage* img, Writer* w) { * thunk pointer is silently patched to _tlv_bootstrap_error. Apple's * ld sets it whenever the image contains S_THREAD_LOCAL_* sections. */ if (x.ntlv) mh_flags |= MH_HAS_TLV_DESCRIPTORS; - mbuf_u32(&file, mh_flags); + objbb_u32(&file, mh_flags); } - mbuf_u32(&file, 0); /* reserved */ - mbuf_append(&file, lc.data, lc.len); + objbb_u32(&file, 0); /* reserved */ + objbb_append(&file, lc.data, lc.len); /* Pad to first section's file offset. */ /* __TEXT first section begins at headers_size; we wrote header+lc = @@ -2624,47 +2564,47 @@ void link_emit_macho(LinkImage* img, Writer* w) { MSec* m = &x.secs[i]; if (m->is_zerofill || m->size == 0) continue; /* Pad up to m->file_offset. */ - while (file.len < m->file_offset) mbuf_u8(&file, 0); + while (file.len < m->file_offset) objbb_u8(&file, 0); if (m->synth_data) { - mbuf_append(&file, m->synth_data, m->synth_size); + objbb_append(&file, m->synth_data, m->synth_size); } else { LinkSection* ls = &img->sections[m->link_sec_id - 1u]; u32 segid = ls->segment_id; u8* base = (segid != LINK_SEG_NONE) ? img->segment_bytes[segid - 1u] : NULL; if (base && ls->size) { - mbuf_append(&file, base + ls->input_offset, (u32)ls->size); + objbb_append(&file, base + ls->input_offset, (u32)ls->size); } else if (ls->size) { - for (u64 k = 0; k < ls->size; ++k) mbuf_u8(&file, 0); + for (u64 k = 0; k < ls->size; ++k) objbb_u8(&file, 0); } } } /* Pad to LINKEDIT start. */ - while (file.len < x.linkedit_fileoff) mbuf_u8(&file, 0); + while (file.len < x.linkedit_fileoff) objbb_u8(&file, 0); /* LINKEDIT contents in declared order. */ - while (file.len < x.chained_fixups_off) mbuf_u8(&file, 0); - mbuf_append(&file, x.chained_fixups.data, x.chained_fixups.len); - while (file.len < x.exports_trie_off) mbuf_u8(&file, 0); - mbuf_append(&file, x.exports_trie.data, x.exports_trie.len); - while (file.len < x.fn_starts_off) mbuf_u8(&file, 0); - mbuf_append(&file, x.fn_starts.data, x.fn_starts.len); - while (file.len < x.data_in_code_off) mbuf_u8(&file, 0); + while (file.len < x.chained_fixups_off) objbb_u8(&file, 0); + objbb_append(&file, x.chained_fixups.data, x.chained_fixups.len); + while (file.len < x.exports_trie_off) objbb_u8(&file, 0); + objbb_append(&file, x.exports_trie.data, x.exports_trie.len); + while (file.len < x.fn_starts_off) objbb_u8(&file, 0); + objbb_append(&file, x.fn_starts.data, x.fn_starts.len); + while (file.len < x.data_in_code_off) objbb_u8(&file, 0); /* empty */ - while (file.len < x.symtab_off) mbuf_u8(&file, 0); - mbuf_append(&file, x.symtab.data, x.symtab.len); - while (file.len < x.indirect_off) mbuf_u8(&file, 0); - mbuf_append(&file, x.indirect.data, x.indirect.len); - while (file.len < x.strtab_off) mbuf_u8(&file, 0); - mbuf_append(&file, x.strtab.data, x.strtab.len); - while (file.len < x.codesig_off) mbuf_u8(&file, 0); + while (file.len < x.symtab_off) objbb_u8(&file, 0); + objbb_append(&file, x.symtab.data, x.symtab.len); + while (file.len < x.indirect_off) objbb_u8(&file, 0); + objbb_append(&file, x.indirect.data, x.indirect.len); + while (file.len < x.strtab_off) objbb_u8(&file, 0); + objbb_append(&file, x.strtab.data, x.strtab.len); + while (file.len < x.codesig_off) objbb_u8(&file, 0); /* Compute codesig hashes over file bytes [0, codesig_off). */ /* The codesig blob currently has zero hashes; hash now. */ compute_codesig(&x, file.data, x.codesig_off, "a.out"); /* Append codesig. */ - mbuf_append(&file, x.codesig.data, x.codesig.len); + objbb_append(&file, x.codesig.data, x.codesig.len); /* Stream out. */ kit_writer_seek(w, 0); @@ -2672,16 +2612,16 @@ void link_emit_macho(LinkImage* img, Writer* w) { /* Cleanup. */ fix_fini(&fl); - mbuf_fini(&lc); - mbuf_fini(&file); - mbuf_fini(&x.chained_fixups); - mbuf_fini(&x.exports_trie); - mbuf_fini(&x.symtab); - mbuf_fini(&x.strtab); - mbuf_fini(&x.indirect); - mbuf_fini(&x.fn_starts); - mbuf_fini(&x.data_in_code); - mbuf_fini(&x.codesig); + objbb_fini(&lc); + objbb_fini(&file); + objbb_fini(&x.chained_fixups); + objbb_fini(&x.exports_trie); + objbb_fini(&x.symtab); + objbb_fini(&x.strtab); + objbb_fini(&x.indirect); + objbb_fini(&x.fn_starts); + objbb_fini(&x.data_in_code); + objbb_fini(&x.codesig); if (x.imports) x.h->free(x.h, x.imports, 0); /* VEC_GROW: cap unknown */ if (x.dylibs) x.h->free(x.h, x.dylibs, 0); if (x.sym_to_imp) diff --git a/src/obj/obj_secnames.c b/src/obj/obj_secnames.c @@ -280,7 +280,7 @@ Sym obj_format_c_mangle(Compiler* c, const char* name) { if (!c || !name) return 0; prefix = obj_format_c_label_prefix(c); plen = slice_from_cstr(prefix).len; - if (plen == 0) return pool_intern_slice(c->global, slice_from_cstr(name)); + if (plen == 0) return pool_intern_cstr(c->global, name); n = slice_from_cstr(name).len; h = (Heap*)c->ctx->heap; buf = (char*)h->alloc(h, n + plen + 1u, 1); diff --git a/src/opt/ir.h b/src/opt/ir.h @@ -564,7 +564,6 @@ typedef enum OptAllocKind { OPT_ALLOC_NONE, OPT_ALLOC_HARD, OPT_ALLOC_SPILL, - OPT_ALLOC_SPLIT, } OptAllocKind; typedef enum OptLocKind { @@ -580,21 +579,6 @@ typedef struct OptLoc { FrameSlot spill_slot; } OptLoc; -typedef struct OptAllocSegment { - u32 start; - u32 end; - u32 block; - u8 loc_kind; - u8 cls; - Reg hard_reg; - FrameSlot spill_slot; - FrameSlot spill_home; - u8 reload_at_start; - u8 store_at_end; - u8 pad[2]; - u32 next; -} OptAllocSegment; - typedef struct OptPRegInfo { u32 first_pos; u32 last_pos; @@ -731,10 +715,6 @@ typedef struct Func { MFunc* mir; /* physical post-allocation IR; HIR stays virtual */ u32* opt_coalesce_parent; u32* opt_coalesce_size; - OptAllocSegment* opt_alloc_segments; - u32 opt_nalloc_segments; - u32 opt_alloc_segments_cap; - u32* opt_first_segment_by_preg; OptUse* opt_uses; u32 opt_nuses, opt_uses_cap; diff --git a/src/opt/opt.c b/src/opt/opt.c @@ -56,6 +56,28 @@ typedef struct OptImpl { HASHMAP_DEFINE(OptFuncIndex, ObjSymId, u32, hash_u32); +/* Section-indexed view of the relocatable-data symbols, built once so the + * data-reloc rooting pass can find the symbol that storage-contains a reloc in + * O(syms-in-section) rather than scanning every symbol per reloc. `entries` is + * packed by section (stable iteration order within a section preserved so the + * "first containing symbol" choice matches the old symiter walk); `by_section` + * maps a section id to its [start, start+count) slice of `entries`. */ +typedef struct OptDataSymEntry { + ObjSymId id; + u64 begin; /* containment range start (s->value) */ + u64 end; /* containment range end (begin + max(size, 1)) */ + int exported; +} OptDataSymEntry; +typedef struct OptSecSlice { + u32 start; + u32 count; +} OptSecSlice; +HASHMAP_DEFINE(OptSecSymIndex, ObjSecId, OptSecSlice, hash_u32); +typedef struct OptDataSymTable { + OptDataSymEntry* entries; + OptSecSymIndex by_section; +} OptDataSymTable; + /* A symbol whose definition can be replaced at link time must not have its body * inlined — the inlined copy would defeat the override. Weak definitions are * interposable in every output kind, so they are never safe to inline. (The @@ -119,8 +141,8 @@ static void opt_dbg_dump(OptImpl* o, Func* f, const char* tag) { kit_writer_mem(o->c->ctx->heap, &w); opt_ir_dump(f, w); bytes = kit_writer_mem_bytes(w, &len); - compiler_panic(o->c, f->desc.loc, "DUMP %s:\n%.*s", tag, (int)len, - (const char*)bytes); + diag_emit(o->c->ctx->diag, KIT_DIAG_NOTE, f->desc.loc, "DUMP %s:\n%.*s", tag, + (int)len, (const char*)bytes); } /* CFG-prep prefix shared by the streaming and whole-program pipelines: lower's @@ -236,7 +258,7 @@ static void opt_o1_native_finish(OptImpl* o, Func* f, int cfg_dirty) { metrics_scope_begin(o->c, "opt.regalloc"); memset(&regalloc_live, 0, sizeof regalloc_live); - opt_regalloc_locations(f, 0, &regalloc_live); + opt_regalloc_locations(f, &regalloc_live); metrics_scope_end(o->c, "opt.regalloc"); metrics_scope_begin(o->c, "opt.regalloc.verify"); opt_analysis_invalidate(f, OPT_ANALYSIS_DEF_USE); @@ -371,8 +393,8 @@ static void opt_dbg_dump_cg(OptImpl* o, const CgIrFunc* f) { kit_writer_mem(o->c->ctx->heap, &w); cg_ir_func_dump(f, w); bytes = kit_writer_mem_bytes(w, &len); - compiler_panic(o->c, f->desc.loc, "CGIR:\n%.*s", (int)len, - (const char*)bytes); + diag_emit(o->c->ctx->diag, KIT_DIAG_NOTE, f->desc.loc, "CGIR:\n%.*s", + (int)len, (const char*)bytes); } static void opt_on_func(void* user, CgIrFunc* cg_func) { @@ -631,37 +653,98 @@ static void opt_mark_data_reloc_graph(OptImpl* o, OptFuncIndex* index, } } -static ObjSymId opt_data_reloc_exported_root_sym(OptImpl* o, const Reloc* r) { +/* Build the section-indexed table of relocatable-data symbols in two passes + * (count per section, then fill), keeping per-section iteration order stable so + * downstream "first containing symbol" lookups match the historical symiter + * walk. `nsym` is the symbol-iterator upper bound already computed by the + * caller. */ +static void opt_data_sym_table_build(OptImpl* o, OptDataSymTable* t, u32 nsym) { ObjSymIter* it; ObjSymEntry ent; + OptSecSymIndex_init_cap(&t->by_section, o->c->ctx->heap, 0); + t->entries = nsym ? arena_array(o->c->tu, OptDataSymEntry, nsym) : NULL; + /* Pass 1: count relocatable-data symbols per section. */ + it = obj_symiter_new(o->target->obj); + while (it && obj_symiter_next(it, &ent)) { + const ObjSym* s = ent.sym; + OptSecSlice* slot; + if (!opt_sym_is_relocatable_data(s)) continue; + slot = OptSecSymIndex_get(&t->by_section, s->section_id); + if (slot) { + slot->count++; + } else { + OptSecSlice sl = {0, 1}; + (void)OptSecSymIndex_set(&t->by_section, s->section_id, sl); + } + } + if (it) obj_symiter_free(it); + /* Assign each section a contiguous [start, start+count) slice and reset the + * counts so pass 2 can use them as fill cursors. */ + { + u32 next = 0; + for (u32 i = 0; i < t->by_section.cap; ++i) { + if (!t->by_section.slots[i].k) continue; + t->by_section.slots[i].v.start = next; + next += t->by_section.slots[i].v.count; + t->by_section.slots[i].v.count = 0; + } + } + /* Pass 2: place symbols in their section slice, preserving iteration order. */ + it = obj_symiter_new(o->target->obj); + while (it && obj_symiter_next(it, &ent)) { + const ObjSym* s = ent.sym; + OptSecSlice* slot; + OptDataSymEntry* e; + if (!opt_sym_is_relocatable_data(s)) continue; + slot = OptSecSymIndex_get(&t->by_section, s->section_id); + if (!slot) continue; /* unreachable: pass 1 inserted every such section */ + e = &t->entries[slot->start + slot->count++]; + e->id = ent.id; + e->begin = s->value; + e->end = s->value + (s->size ? s->size : 1u); + e->exported = s->bind != SB_LOCAL || (s->flags & KIT_CG_SYM_USED) ? 1 : 0; + } + if (it) obj_symiter_free(it); +} + +static void opt_data_sym_table_fini(OptDataSymTable* t) { + OptSecSymIndex_fini(&t->by_section); +} + +/* Find the data symbol whose storage contains reloc `r`, restricted to roots: + * if the reloc's section is RETAIN any containing symbol roots it, otherwise + * only an exported symbol does. Returns the first match in the section's stable + * order (matching the old per-symbol scan). */ +static ObjSymId opt_data_reloc_exported_root_sym(OptImpl* o, + const OptDataSymTable* t, + const Reloc* r) { const Section* sec; + const OptSecSlice* slice; + int retained; if (!r || r->removed || r->section_id == OBJ_SEC_NONE) return OBJ_SYM_NONE; sec = obj_section_get(o->target->obj, r->section_id); if (!sec || sec->removed || sec->kind == SEC_TEXT) return OBJ_SYM_NONE; - it = obj_symiter_new(o->target->obj); - if (!it) return OBJ_SYM_NONE; - while (obj_symiter_next(it, &ent)) { - const ObjSym* s = ent.sym; - if (!opt_reloc_inside_sym(r, s)) continue; - if (sec->flags & SF_RETAIN) { - obj_symiter_free(it); - return ent.id; - } - if (s->bind == SB_LOCAL && !(s->flags & KIT_CG_SYM_USED)) continue; - obj_symiter_free(it); - return ent.id; + slice = OptSecSymIndex_get(&t->by_section, r->section_id); + if (!slice) return OBJ_SYM_NONE; + retained = (sec->flags & SF_RETAIN) ? 1 : 0; + for (u32 i = 0; i < slice->count; ++i) { + const OptDataSymEntry* e = &t->entries[slice->start + i]; + if ((u64)r->offset < e->begin || (u64)r->offset >= e->end) continue; + if (retained) return e->id; + if (!e->exported) continue; + return e->id; } - obj_symiter_free(it); return OBJ_SYM_NONE; } -static void opt_root_exported_data_relocs(OptImpl* o, ObjSymSet* data_seen, +static void opt_root_exported_data_relocs(OptImpl* o, const OptDataSymTable* t, + ObjSymSet* data_seen, ObjSymId* data_queue, u32* data_qtail) { u32 nrel = obj_reloc_total(o->target->obj); for (u32 i = 0; i < nrel; ++i) { const Reloc* r = obj_reloc_at(o->target->obj, i); - ObjSymId sym = opt_data_reloc_exported_root_sym(o, r); + ObjSymId sym = opt_data_reloc_exported_root_sym(o, t, r); if (sym != OBJ_SYM_NONE) opt_enqueue_data_sym(o, data_seen, data_queue, data_qtail, sym); } @@ -716,6 +799,7 @@ static void opt_whole_module_finalize(OptImpl* o, const CgIrModule* module, OptFuncIndex index; ObjSymSet preserved; ObjSymSet data_seen; + OptDataSymTable data_syms; u8* reachable; u8* queued; u32* queue; @@ -739,6 +823,7 @@ static void opt_whole_module_finalize(OptImpl* o, const CgIrModule* module, if (it) obj_symiter_free(it); } data_queue = arena_array(o->c->tu, ObjSymId, nsym); + opt_data_sym_table_build(o, &data_syms, nsym); opt_resolve_duplicate_funcs(o, module, &index); opt_build_preserved_set(o, &preserved); opt_internalize_non_preserved(o, module, &preserved); @@ -748,7 +833,8 @@ static void opt_whole_module_finalize(OptImpl* o, const CgIrModule* module, opt_mark_func(reachable, queued, queue, &qtail, i); } opt_root_aliases(o, module, &index, reachable, queued, queue, &qtail); - opt_root_exported_data_relocs(o, &data_seen, data_queue, &data_qtail); + opt_root_exported_data_relocs(o, &data_syms, &data_seen, data_queue, + &data_qtail); opt_mark_data_reloc_graph(o, &index, reachable, queued, queue, &qtail, &data_seen, data_queue, &data_qhead, &data_qtail); while (qhead < qtail) { @@ -760,6 +846,7 @@ static void opt_whole_module_finalize(OptImpl* o, const CgIrModule* module, opt_mark_data_reloc_graph(o, &index, reachable, queued, queue, &qtail, &data_seen, data_queue, &data_qhead, &data_qtail); } + opt_data_sym_table_fini(&data_syms); for (u32 i = 0; i < module->nfuncs; ++i) { CgIrFunc* cg_func = module->funcs[i]; if (cg_func && cg_func->removed) continue; diff --git a/src/opt/opt.h b/src/opt/opt.h @@ -145,9 +145,7 @@ void opt_ir_dump(Func*, Writer*); void opt_ssa_dump(Func*, Writer*); void opt_rewrite_dump(Func*, Writer*); void opt_coalesce(Func*); -void opt_regalloc_locations(Func*, int allow_live_range_split, - OptLiveInfo* live_out); -void opt_regalloc(Func*, int allow_live_range_split); +void opt_regalloc_locations(Func*, OptLiveInfo* live_out); void opt_lower_to_mir(Func*, const OptLiveInfo*); void opt_mir_combine(Func*); void opt_mir_dce(Func*); diff --git a/src/opt/opt_internal.h b/src/opt/opt_internal.h @@ -16,13 +16,6 @@ typedef struct OptHardBlockLive { OptHardRegSet live_def; } OptHardBlockLive; -typedef struct OptPassCtx { - Compiler* c; - Func* f; - Arena* arena; - const char* stage; -} OptPassCtx; - typedef struct FuncSet FuncSet; struct FuncSet { Compiler* c; @@ -55,7 +48,9 @@ typedef struct OptAnalysis { } OptAnalysis; typedef enum OptAnalysisFlag { - OPT_ANALYSIS_CFG = 1u << 0, + /* OPT_ANALYSIS_CFG was tracked but never queried (opt_build_cfg always + * rebuilt unconditionally), so the bit and its maintenance were removed. + * Values stay stable to keep any persisted masks unambiguous. */ OPT_ANALYSIS_DEF_USE = 1u << 1, OPT_ANALYSIS_DOM = 1u << 2, OPT_ANALYSIS_LOOP = 1u << 3, diff --git a/src/opt/pass_analysis.c b/src/opt/pass_analysis.c @@ -634,8 +634,7 @@ static void verify_allocations(Func* f, const char* stage) { u8 cls = opt_preg_loc_cls(f, r); if (cls >= OPT_REG_CLASSES) opt_fail(f, stage, "bad preg alloc class", r, cls); - if (pi && pi->alloc_kind != alloc_kind && - !(pi->alloc_kind == OPT_ALLOC_SPLIT && alloc_kind == OPT_ALLOC_SPLIT)) + if (pi && pi->alloc_kind != alloc_kind) opt_fail(f, stage, "alloc kind mirror mismatch", r, pi->alloc_kind); switch ((OptAllocKind)alloc_kind) { case OPT_ALLOC_NONE: @@ -665,38 +664,11 @@ static void verify_allocations(Func* f, const char* stage) { opt_fail(f, stage, "spill alloc location mismatch", r, opt_preg_spill_slot(f, r)); break; - case OPT_ALLOC_SPLIT: - if (!f->opt_first_segment_by_preg) - opt_fail(f, stage, "split allocation missing segments", r, 0); - if (loc && loc->kind != OPT_LOC_NONE) - opt_fail(f, stage, "split alloc location mismatch", r, loc->kind); - break; default: opt_fail(f, stage, "bad allocation kind", r, alloc_kind); break; } } - if (f->opt_first_segment_by_preg && f->opt_alloc_segments) { - for (PReg r = 1; r < opt_reg_count(f); ++r) { - for (u32 si = f->opt_first_segment_by_preg[r]; si != OPT_RANGE_NONE; - si = f->opt_alloc_segments[si].next) { - OptAllocSegment* s = &f->opt_alloc_segments[si]; - if (s->block >= f->nblocks) - opt_fail(f, stage, "bad split block", r, si); - if (s->start > s->end) opt_fail(f, stage, "bad split range", r, si); - if (s->cls >= OPT_REG_CLASSES) - opt_fail(f, stage, "bad split class", r, s->cls); - if (s->loc_kind == OPT_LOC_HARD) { - if (s->hard_reg == (Reg)REG_NONE || s->hard_reg >= OPT_MAX_HARD_REGS) - opt_fail(f, stage, "bad split hard reg", r, s->hard_reg); - } else if (s->loc_kind == OPT_LOC_STACK) { - verify_frame_slot(f, stage, s->spill_slot, "bad split spill slot"); - } else if (s->loc_kind != OPT_LOC_NONE) { - opt_fail(f, stage, "bad split loc kind", r, s->loc_kind); - } - } - } - } } static void verify_rewritten(Func* f, const char* stage) { diff --git a/src/opt/pass_cfg.c b/src/opt/pass_cfg.c @@ -216,8 +216,8 @@ u32 opt_split_edge(Func* f, u32 pred, u32 succ) { } else { ir_note_emit(f, edge); } - opt_analysis_invalidate(f, OPT_ANALYSIS_CFG | OPT_ANALYSIS_DEF_USE | - OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); + opt_analysis_invalidate( + f, OPT_ANALYSIS_DEF_USE | OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); return edge; } @@ -305,5 +305,4 @@ void opt_build_cfg(Func* f) { f->blocks[t].preds[f->blocks[t].npreds++] = b; } } - opt_analysis_mark_valid(f, OPT_ANALYSIS_CFG); } diff --git a/src/opt/pass_copy.c b/src/opt/pass_copy.c @@ -26,21 +26,6 @@ static int copy_values(const Inst* in, Val* dst, Val* src) { return 1; } -static u32 def_count(Func* f, Val v) { - u32 n = 0; - if (v == VAL_NONE) return 0; - for (u32 b = 0; b < f->nblocks; ++b) { - Block* bl = &f->blocks[b]; - for (u32 i = 0; i < bl->ninsts; ++i) { - Inst* in = &bl->insts[i]; - if (in->def == v) ++n; - for (u32 d = 0; d < in->ndefs; ++d) - if (in->defs[d] == v) ++n; - } - } - return n; -} - static void replace_one_use(Func* f, const OptUse* use, Val src) { Inst* in = &f->blocks[use->block].insts[use->inst]; switch ((OptUseKind)use->kind) { @@ -206,8 +191,54 @@ static int simplify_convert_chain(Func* f, Inst* outer, const ConvertStep* o, return 0; } -static int cleanup_one_copy(Func* f) { +/* Precompute the number of definitions of every value in a single sweep so the + * batch copy pass can test the def_count==1 removability condition in O(1) + * instead of rescanning the whole function per copy. */ +static void compute_def_counts(Func* f, u32* ndef) { + memset(ndef, 0, sizeof(*ndef) * f->nvals); + for (u32 b = 0; b < f->nblocks; ++b) { + Block* bl = &f->blocks[b]; + for (u32 i = 0; i < bl->ninsts; ++i) { + Inst* in = &bl->insts[i]; + if (in->def != VAL_NONE && in->def < f->nvals) ++ndef[in->def]; + for (u32 d = 0; d < in->ndefs; ++d) + if (in->defs[d] != VAL_NONE && in->defs[d] < f->nvals) + ++ndef[in->defs[d]]; + } + } +} + +/* Follow the removed-copy chain dst -> src to its final representative: a value + * that is not itself the dst of a removed copy (or a self-copy). `repl[v]` is + * VAL_NONE when v is not a removed-copy dst. Bounded by nvals to stay safe if a + * pathological IR forms a copy cycle. */ +static Val resolve_copy(Func* f, const Val* repl, Val v) { + for (u32 hops = 0; hops < f->nvals; ++hops) { + Val next; + if (v == VAL_NONE || v >= f->nvals) break; + next = repl[v]; + if (next == VAL_NONE || next == v) break; + v = next; + } + return v; +} + +/* Collect every removable IR_COPY in one def-use pass, resolve copy chains to + * their final source, then redirect all uses and drop the copies together — + * O(N) rather than the O(N^2) "rebuild + remove one" loop. Removing a copy + * never changes another value's def_count (a removable non-self copy is the + * sole def of its dst), so a single pass reaches the fixpoint. */ +static int cleanup_copies_pass(Func* f) { + u32* ndef; + Val* repl; + int removed_any = 0; opt_rebuild_def_use(f); + if (!f->nvals) return 0; + ndef = arena_array(f->arena, u32, f->nvals); + repl = arena_array(f->arena, Val, f->nvals); + for (u32 v = 0; v < f->nvals; ++v) repl[v] = VAL_NONE; + compute_def_counts(f, ndef); + /* First sweep: tag each removable copy's dst with its immediate src. */ for (u32 b = 0; b < f->nblocks; ++b) { Block* bl = &f->blocks[b]; for (u32 i = 0; i < bl->ninsts; ++i) { @@ -215,18 +246,35 @@ static int cleanup_one_copy(Func* f) { Val dst = VAL_NONE; Val src = VAL_NONE; if (!copy_values(in, &dst, &src)) continue; + if (dst == VAL_NONE || dst >= f->nvals) continue; if (dst != src && !same_val_shape(f, dst, src)) continue; - if (dst != src && def_count(f, dst) != 1) continue; - for (u32 u = f->opt_first_use_by_val[dst]; u != OPT_USE_NONE; - u = f->opt_uses[u].next_for_val) - replace_one_use(f, &f->opt_uses[u], src); + if (dst != src && ndef[dst] != 1) continue; + repl[dst] = src; + removed_any = 1; + } + } + if (!removed_any) return 0; + /* Second sweep: redirect uses to the resolved source and erase the copies. */ + for (u32 b = 0; b < f->nblocks; ++b) { + Block* bl = &f->blocks[b]; + for (u32 i = 0; i < bl->ninsts; ++i) { + Inst* in = &bl->insts[i]; + Val dst = VAL_NONE; + Val src = VAL_NONE; + Val final_src; + if (!copy_values(in, &dst, &src)) continue; + if (dst >= f->nvals || repl[dst] == VAL_NONE) continue; + final_src = resolve_copy(f, repl, dst); + if (final_src != dst) + for (u32 u = f->opt_first_use_by_val[dst]; u != OPT_USE_NONE; + u = f->opt_uses[u].next_for_val) + replace_one_use(f, &f->opt_uses[u], final_src); remove_copy_inst(in); - opt_analysis_invalidate(f, OPT_ANALYSIS_DEF_USE); - compact_copies(f); - return 1; } } - return 0; + opt_analysis_invalidate(f, OPT_ANALYSIS_DEF_USE); + compact_copies(f); + return 1; } static int cleanup_one_extension(Func* f) { @@ -255,7 +303,7 @@ static int cleanup_one_extension(Func* f) { void opt_copy_cleanup(Func* f) { if (!f || f->opt_rewritten) return; - while (cleanup_one_copy(f)) { + while (cleanup_copies_pass(f)) { } opt_rebuild_def_use(f); } diff --git a/src/opt/pass_inline.c b/src/opt/pass_inline.c @@ -585,8 +585,8 @@ static int inline_call_site(Func* caller, u32 block_idx, u32 inst_idx, } inline_rebuild_emit_order(caller, block_idx, &map, cont); - opt_analysis_invalidate(caller, OPT_ANALYSIS_CFG | OPT_ANALYSIS_DEF_USE | - OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); + opt_analysis_invalidate( + caller, OPT_ANALYSIS_DEF_USE | OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); return 1; } diff --git a/src/opt/pass_jump.c b/src/opt/pass_jump.c @@ -589,8 +589,8 @@ static int full_collapse_same_target_branches(Func* f) { void opt_jump_cleanup(Func* f, OptJumpCleanupStage stage) { if (!f) return; - opt_analysis_invalidate(f, OPT_ANALYSIS_CFG | OPT_ANALYSIS_DEF_USE | - OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); + opt_analysis_invalidate( + f, OPT_ANALYSIS_DEF_USE | OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); JumpCleanupCtx c = jump_cleanup_ctx(f); if (stage == OPT_JUMP_CLEANUP_CFG) { cleanup_invert_jump_fallthrough(&c); @@ -607,8 +607,8 @@ void opt_jump_opt(Func* f) { if (!f) return; int changed = 0; - opt_analysis_invalidate(f, OPT_ANALYSIS_CFG | OPT_ANALYSIS_DEF_USE | - OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); + opt_analysis_invalidate( + f, OPT_ANALYSIS_DEF_USE | OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); for (u32 iter = 0; iter < f->nblocks; ++iter) { JumpCleanupCtx c = jump_cleanup_ctx(f); @@ -625,8 +625,8 @@ void opt_jump_opt(Func* f) { } if (changed) { - opt_analysis_invalidate(f, OPT_ANALYSIS_CFG | OPT_ANALYSIS_DEF_USE | - OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); + opt_analysis_invalidate( + f, OPT_ANALYSIS_DEF_USE | OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); } opt_build_cfg(f); } diff --git a/src/opt/pass_lower.c b/src/opt/pass_lower.c @@ -586,14 +586,6 @@ static FrameSlot spill_slot_for(Func* f, PReg v) { static u32 hard_loc_bit(u8 cls, Reg r) { return ((u32)cls * 32u) + (u32)r; } -typedef struct OptAllocCandidate { - PReg v; /* coalesce-root PReg with live ranges */ - u32 spill_cost; - u32 live_length; - u8 tied; - u8 pad[3]; -} OptAllocCandidate; - typedef struct OptAllocGroupInfo { PReg root; u32 spill_cost; @@ -607,6 +599,13 @@ typedef struct OptAllocGroupInfo { u8 pad[3]; } OptAllocGroupInfo; +typedef struct OptAllocCandidate { + PReg v; /* coalesce-root PReg with live ranges */ + /* Group info computed once during candidate collection and reused by the + * assignment loop, which would otherwise recompute it per candidate. */ + OptAllocGroupInfo gi; +} OptAllocCandidate; + typedef struct OptAllocator { OptLoc* locs; /* per-PReg result (cls, hard_reg, spill_slot) */ @@ -714,9 +713,13 @@ static u32 hard_reg_alloc_score(Func* f, const OptAllocator* a, static int alloc_candidate_higher(const OptAllocCandidate* a, const OptAllocCandidate* b) { - if (a->tied != b->tied) return a->tied > b->tied; - if (a->spill_cost != b->spill_cost) return a->spill_cost > b->spill_cost; - if (a->live_length != b->live_length) return a->live_length < b->live_length; + int a_tied = a->gi.tied_hard_reg >= 0; + int b_tied = b->gi.tied_hard_reg >= 0; + if (a_tied != b_tied) return a_tied > b_tied; + if (a->gi.spill_cost != b->gi.spill_cost) + return a->gi.spill_cost > b->gi.spill_cost; + if (a->gi.live_length != b->gi.live_length) + return a->gi.live_length < b->gi.live_length; return a->v < b->v; } @@ -1037,10 +1040,7 @@ static int alloc_group_conflicts_bit(const OptAllocator* a, u32 bit) { } static void opt_assign_ranges(Func* f, const OptLiveRangeSet* ranges, - OptAllocator* a, int allow_live_range_split) { - (void)allow_live_range_split; /* live-range splitting deferred per - doc/plan/OPTIMIZER.md; the parameter is - passed through for ABI compatibility. */ + OptAllocator* a) { memset(a, 0, sizeof *a); a->point_count = ranges->point_count ? ranges->point_count : 1u; a->hard_loc_bits = OPT_REG_CLASSES * 32u; @@ -1069,21 +1069,15 @@ static void opt_assign_ranges(Func* f, const OptLiveRangeSet* ranges, if (ranges->first_range_by_preg[v] == OPT_RANGE_NONE) continue; PReg root = alloc_coalesce_root(f, v); if (root != v) continue; - OptAllocGroupInfo gi; - alloc_group_info(f, ranges, root, &gi); cands[n].v = v; - cands[n].spill_cost = gi.spill_cost; - cands[n].live_length = gi.live_length; - cands[n].tied = gi.tied_hard_reg >= 0; - memset(cands[n].pad, 0, sizeof cands[n].pad); + alloc_group_info(f, ranges, root, &cands[n].gi); ++n; } alloc_sort_candidates(cands, n); for (u32 i = 0; i < n; ++i) { PReg v = cands[i].v; - OptAllocGroupInfo gi; - alloc_group_info(f, ranges, v, &gi); + OptAllocGroupInfo gi = cands[i].gi; OptPRegInfo* vi = &f->preg_info[v]; u8 cls = gi.cls; alloc_compute_group_conflicts(f, a, ranges, v); @@ -1257,26 +1251,6 @@ typedef struct RewriteList { u32 cap; } RewriteList; -typedef enum EdgeMatKind { - EDGE_MAT_STORE, - EDGE_MAT_RELOAD, -} EdgeMatKind; - -typedef struct EdgeMat { - u32 pred; - u32 succ; - PReg v; - Reg hard_reg; - u8 kind; - u8 pad[3]; -} EdgeMat; - -typedef struct EdgeMatPlan { - EdgeMat* mats; - u32 n; - u32 cap; -} EdgeMatPlan; - typedef struct RewriteOut { Inst* data; u32 cap; @@ -1371,16 +1345,6 @@ static Operand hard_operand(Func* f, PReg v) { return o; } -static Operand hard_operand_reg(Func* f, PReg v, Reg hard_reg) { - Operand o; - memset(&o, 0, sizeof o); - o.kind = OPK_REG; - o.cls = opt_preg_loc_cls(f, v); - o.type = opt_reg_type(f, v); - o.v.reg = hard_reg; - return o; -} - static void append_store_preg(Func* f, RewriteList* out, PReg v) { Inst* st = list_push(f, out, IR_STORE); st->opnds = arena_array(f->arena, Operand, 2); @@ -1399,26 +1363,6 @@ static void append_load_preg(Func* f, RewriteList* out, PReg v) { ld->extra.mem = spill_mem(f, v); } -static void append_store_preg_hard(Func* f, RewriteList* out, PReg v, - Reg hard_reg) { - Inst* st = list_push(f, out, IR_STORE); - st->opnds = arena_array(f->arena, Operand, 2); - st->opnds[0] = spill_addr(f, v); - st->opnds[1] = hard_operand_reg(f, v, hard_reg); - st->nopnds = 2; - st->extra.mem = spill_mem(f, v); -} - -static void append_load_preg_hard(Func* f, RewriteList* out, PReg v, - Reg hard_reg) { - Inst* ld = list_push(f, out, IR_LOAD); - ld->opnds = arena_array(f->arena, Operand, 2); - ld->opnds[0] = hard_operand_reg(f, v, hard_reg); - ld->opnds[1] = spill_addr(f, v); - ld->nopnds = 2; - ld->extra.mem = spill_mem(f, v); -} - static Reg scratch_for(Func* f, u8 cls, u32* next) { u32 n = f->opt_scratch_reg_count[cls]; if (!n) return REG_NONE; @@ -1431,19 +1375,8 @@ typedef struct RewriteCtx { RewriteList* before; RewriteList* after; u32 next_scratch[OPT_REG_CLASSES]; - u32 raw_point; } RewriteCtx; -static const OptAllocSegment* split_segment_at(Func* f, PReg v, u32 raw_point) { - if (!f->opt_first_segment_by_preg || v >= opt_reg_count(f)) return NULL; - for (u32 si = f->opt_first_segment_by_preg[v]; si != OPT_RANGE_NONE; - si = f->opt_alloc_segments[si].next) { - const OptAllocSegment* s = &f->opt_alloc_segments[si]; - if (s->start <= raw_point && raw_point < s->end) return s; - } - return NULL; -} - static void rewrite_one_operand(Func* f, Inst* owner, Operand* op, int is_def, void* arg) { RewriteCtx* c = (RewriteCtx*)arg; @@ -1455,15 +1388,7 @@ static void rewrite_one_operand(Func* f, Inst* owner, Operand* op, int is_def, op->v.reg = opt_preg_hard_reg(f, v); return; } - if (alloc_kind == OPT_ALLOC_SPLIT) { - const OptAllocSegment* seg = split_segment_at(f, v, c->raw_point); - if (seg && seg->loc_kind == OPT_LOC_HARD) { - op->v.reg = seg->hard_reg; - return; - } - } else if (alloc_kind != OPT_ALLOC_SPILL) { - return; - } + if (alloc_kind != OPT_ALLOC_SPILL) return; u8 cls = opt_preg_loc_cls(f, v); Reg scratch = scratch_for(f, cls, &c->next_scratch[cls]); if (scratch == (Reg)REG_NONE) { @@ -1497,7 +1422,7 @@ static void rewrite_call_arg_operand(Func* f, Operand* op) { u8 alloc_kind = opt_preg_alloc_kind(f, v); if (alloc_kind == OPT_ALLOC_HARD) { op->v.reg = opt_preg_hard_reg(f, v); - } else if (alloc_kind == OPT_ALLOC_SPILL || alloc_kind == OPT_ALLOC_SPLIT) { + } else if (alloc_kind == OPT_ALLOC_SPILL) { *op = spill_addr(f, v); } } @@ -1506,7 +1431,6 @@ static void rewrite_store_value_operand(Func* f, Inst* owner, Operand* op, RewriteCtx* ctx) { PReg v; u8 alloc_kind; - const OptAllocSegment* seg; if (!op || op->kind != OPK_REG) return; v = (PReg)op->v.reg; if (v == PREG_NONE || v == 0 || v >= opt_reg_count(f)) return; @@ -1515,15 +1439,6 @@ static void rewrite_store_value_operand(Func* f, Inst* owner, Operand* op, op->v.reg = opt_preg_hard_reg(f, v); return; } - if (alloc_kind == OPT_ALLOC_SPLIT) { - seg = split_segment_at(f, v, ctx->raw_point); - if (seg && seg->loc_kind == OPT_LOC_HARD) { - op->v.reg = seg->hard_reg; - return; - } - *op = spill_addr(f, v); - return; - } if (alloc_kind == OPT_ALLOC_SPILL) { *op = spill_addr(f, v); return; @@ -1614,186 +1529,6 @@ static PReg* rewrite_collect_call_save_pregs(Func* f, u32* count_out) { return pregs; } -static void append_split_reloads_at(Func* f, RewriteList* out, u32 block, - u32 raw_point) { - if (!f->opt_first_segment_by_preg) return; - for (PReg v = 1; v < opt_reg_count(f); ++v) { - if (opt_preg_alloc_kind(f, v) != OPT_ALLOC_SPLIT) continue; - for (u32 si = f->opt_first_segment_by_preg[v]; si != OPT_RANGE_NONE; - si = f->opt_alloc_segments[si].next) { - const OptAllocSegment* s = &f->opt_alloc_segments[si]; - if (s->block == block && s->start == raw_point && s->reload_at_start && - s->loc_kind == OPT_LOC_HARD) { - append_load_preg_hard(f, out, v, s->hard_reg); - } - } - } -} - -static void append_split_stores_at(Func* f, RewriteList* out, u32 block, - u32 raw_point) { - if (!f->opt_first_segment_by_preg) return; - for (PReg v = 1; v < opt_reg_count(f); ++v) { - if (opt_preg_alloc_kind(f, v) != OPT_ALLOC_SPLIT) continue; - for (u32 si = f->opt_first_segment_by_preg[v]; si != OPT_RANGE_NONE; - si = f->opt_alloc_segments[si].next) { - const OptAllocSegment* s = &f->opt_alloc_segments[si]; - if (s->block == block && s->end == raw_point && s->store_at_end && - s->loc_kind == OPT_LOC_HARD) { - append_store_preg_hard(f, out, v, s->hard_reg); - } - } - } -} - -static void edge_mat_push(Func* f, EdgeMatPlan* plan, u32 pred, u32 succ, - PReg v, Reg hard_reg, EdgeMatKind kind) { - if (!plan || pred >= f->nblocks || succ >= f->nblocks) return; - if (hard_reg == (Reg)REG_NONE) return; - if (plan->n == plan->cap) { - u32 ncap = plan->cap ? plan->cap * 2u : 16u; - EdgeMat* nm = arena_array(f->arena, EdgeMat, ncap); - if (plan->mats) memcpy(nm, plan->mats, sizeof(plan->mats[0]) * plan->n); - plan->mats = nm; - plan->cap = ncap; - } - EdgeMat* m = &plan->mats[plan->n++]; - memset(m, 0, sizeof *m); - m->pred = pred; - m->succ = succ; - m->v = v; - m->hard_reg = hard_reg; - m->kind = (u8)kind; -} - -static void prepare_split_edge_materialization(Func* f, EdgeMatPlan* plan) { - if (!f || !plan || !f->opt_first_segment_by_preg) return; - u32* block_base = arena_array(f->arena, u32, f->nblocks ? f->nblocks : 1u); - u32 raw = 0; - for (u32 b = 0; b < f->nblocks; ++b) { - block_base[b] = raw; - raw += f->blocks[b].ninsts ? f->blocks[b].ninsts : 1u; - } - - for (PReg v = 1; v < opt_reg_count(f); ++v) { - if (opt_preg_alloc_kind(f, v) != OPT_ALLOC_SPLIT) continue; - for (u32 si = f->opt_first_segment_by_preg[v]; si != OPT_RANGE_NONE; - si = f->opt_alloc_segments[si].next) { - OptAllocSegment* s = &f->opt_alloc_segments[si]; - if (s->loc_kind != OPT_LOC_HARD || s->block >= f->nblocks) continue; - Block* bl = &f->blocks[s->block]; - u32 block_start = block_base[s->block]; - u32 block_end = block_start + (bl->ninsts ? bl->ninsts : 1u); - if (s->reload_at_start && s->start == block_start && bl->npreds) { - for (u32 p = 0; p < bl->npreds; ++p) - edge_mat_push(f, plan, bl->preds[p], s->block, v, s->hard_reg, - EDGE_MAT_RELOAD); - s->reload_at_start = 0; - } - if (s->store_at_end && s->end == block_end && bl->nsucc) { - for (u32 e = 0; e < bl->nsucc; ++e) - edge_mat_push(f, plan, s->block, bl->succ[e], v, s->hard_reg, - EDGE_MAT_STORE); - s->store_at_end = 0; - } - } - } -} - -static int lower_is_terminator(const Inst* in) { - if (!in) return 0; - switch ((IROp)in->op) { - case IR_BR: - case IR_CONDBR: - case IR_CMP_BRANCH: - case IR_SWITCH: - case IR_INDIRECT_BRANCH: - case IR_RET: - case IR_UNREACHABLE: - case IR_BREAK_TO: - case IR_CONTINUE_TO: - return 1; - case IR_INTRINSIC: { - IRIntrinAux* aux = (IRIntrinAux*)in->extra.aux; - return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP); - } - default: - return 0; - } -} - -static void block_insert_list(Func* f, u32 block, const RewriteList* list) { - if (!f || block >= f->nblocks || !list || !list->n) return; - Block* bl = &f->blocks[block]; - u32 term = bl->ninsts && lower_is_terminator(&bl->insts[bl->ninsts - 1u]); - u32 insert_at = bl->ninsts - term; - Inst* insts = arena_zarray(f->arena, Inst, bl->ninsts + list->n); - if (insert_at) memcpy(insts, bl->insts, sizeof(Inst) * insert_at); - memcpy(insts + insert_at, list->data, sizeof(Inst) * list->n); - if (bl->ninsts > insert_at) { - memcpy(insts + insert_at + list->n, bl->insts + insert_at, - sizeof(Inst) * (bl->ninsts - insert_at)); - } - bl->insts = insts; - bl->ninsts += list->n; - bl->cap = bl->ninsts; -} - -typedef struct EdgePlace { - u32 pred; - u32 succ; - u32 block; -} EdgePlace; - -static u32 edge_place_for(Func* f, EdgePlace** places, u32* nplaces, u32* cap, - u32 pred, u32 succ) { - for (u32 i = 0; i < *nplaces; ++i) - if ((*places)[i].pred == pred && (*places)[i].succ == succ) - return (*places)[i].block; - if (*nplaces == *cap) { - u32 ncap = *cap ? *cap * 2u : 16u; - EdgePlace* np = arena_array(f->arena, EdgePlace, ncap); - if (*places) memcpy(np, *places, sizeof((*places)[0]) * *nplaces); - *places = np; - *cap = ncap; - } - u32 block = opt_split_edge(f, pred, succ); - EdgePlace* p = &(*places)[(*nplaces)++]; - p->pred = pred; - p->succ = succ; - p->block = block; - return block; -} - -static void apply_split_edge_materialization(Func* f, const EdgeMatPlan* plan) { - if (!f || !plan || !plan->n) return; - EdgePlace* places = NULL; - u32 nplaces = 0; - u32 place_cap = 0; - for (u32 i = 0; i < plan->n; ++i) { - const EdgeMat* m = &plan->mats[i]; - if (m->pred < f->nblocks && m->succ < f->nblocks) - (void)edge_place_for(f, &places, &nplaces, &place_cap, m->pred, m->succ); - } - for (u32 pass = 0; pass < 2; ++pass) { - EdgeMatKind kind = pass == 0 ? EDGE_MAT_STORE : EDGE_MAT_RELOAD; - for (u32 i = 0; i < plan->n; ++i) { - const EdgeMat* m = &plan->mats[i]; - if ((EdgeMatKind)m->kind != kind) continue; - u32 block = - edge_place_for(f, &places, &nplaces, &place_cap, m->pred, m->succ); - RewriteList list; - memset(&list, 0, sizeof list); - if (kind == EDGE_MAT_STORE) - append_store_preg_hard(f, &list, m->v, m->hard_reg); - else - append_load_preg_hard(f, &list, m->v, m->hard_reg); - block_insert_list(f, block, &list); - } - } - opt_build_cfg(f); -} - static void rewrite_func(Func* f, const OptLiveInfo* live_info) { u32 words = live_info ? live_info->words : f->opt_live_words; if (!words) words = bit_words(opt_reg_count(f)); @@ -1807,12 +1542,6 @@ static void rewrite_func(Func* f, const OptLiveInfo* live_info) { InstRefs refs; memset(&refs, 0, sizeof refs); u32 live_active_words = 0; - u32* block_base = arena_array(f->arena, u32, f->nblocks ? f->nblocks : 1u); - u32 raw = 0; - for (u32 b = 0; b < f->nblocks; ++b) { - block_base[b] = raw; - raw += f->blocks[b].ninsts ? f->blocks[b].ninsts : 1u; - } for (u32 b = 0; b < f->nblocks; ++b) { Block* bl = &f->blocks[b]; RewriteOut out; @@ -1843,7 +1572,6 @@ static void rewrite_func(Func* f, const OptLiveInfo* live_info) { memset(&ctx, 0, sizeof ctx); ctx.before = &before; ctx.after = &after; - ctx.raw_point = block_base[b] + i; if ((IROp)in.op == IR_CALL) { IRCallAux* aux = (IRCallAux*)in.extra.aux; if (aux) { @@ -1877,9 +1605,6 @@ static void rewrite_func(Func* f, const OptLiveInfo* live_info) { append_live_call_saves(f, &call_restores, &in, live, live_active_words, &refs, call_save_pregs, ncall_save_pregs, 1); } - append_split_reloads_at(f, &before, b, block_base[b] + i); - append_split_stores_at(f, &after, b, block_base[b] + i + 1u); - out_prepend_list_reverse(f, &out, &after); out_prepend_list_reverse(f, &out, &call_restores); out_prepend_inst(f, &out, &in); @@ -2116,8 +1841,7 @@ static void opt_verify_alloc(Func* f, const OptLiveInfo* live) { } } -static void opt_regalloc_place(Func* f, int allow_live_range_split, - OptLiveInfo* live_out) { +static void opt_regalloc_place(Func* f, OptLiveInfo* live_out) { metrics_scope_begin(f->c, "opt.live_ranges.regalloc"); OptLiveInfo live; opt_live_blocks(f, &live); @@ -2134,10 +1858,9 @@ static void opt_regalloc_place(Func* f, int allow_live_range_split, for (PReg v = 1; v < opt_reg_count(f); ++v) f->preg_info[v].forbidden_hard_regs |= f->preg_info[v].clobbered_hard_regs; - /* MIR coalesces only at -O2 (mir-gen.c:9431); match that here. At O1 the - * point-bitmap allocator emits copies through the natural conflict-free - * path. IRF_NO_COALESCE protects SSA edge copies inserted at O2. */ - if (allow_live_range_split) opt_coalesce_ranges(f, &ranges); + /* This O1 point-bitmap allocator does not coalesce or split live ranges; it + * emits copies through the natural conflict-free path. MIR coalesces at -O2 + * (mir-gen.c). */ metrics_count(f->c, "opt.live_words", f->opt_live_words); metrics_count(f->c, "opt.ranges", ranges.nranges); metrics_count(f->c, "opt.range_points", ranges.point_count); @@ -2158,22 +1881,11 @@ static void opt_regalloc_place(Func* f, int allow_live_range_split, metrics_scope_end(f->c, "opt.live_ranges.regalloc"); OptAllocator alloc; - opt_assign_ranges(f, &ranges, &alloc, allow_live_range_split); - if (!allow_live_range_split) opt_verify_alloc(f, &live); + opt_assign_ranges(f, &ranges, &alloc); + opt_verify_alloc(f, &live); if (live_out) *live_out = live; } -void opt_regalloc_locations(Func* f, int allow_live_range_split, - OptLiveInfo* live_out) { - opt_regalloc_place(f, allow_live_range_split, live_out); -} - -void opt_regalloc(Func* f, int allow_live_range_split) { - OptLiveInfo live; - opt_regalloc_place(f, allow_live_range_split, &live); - EdgeMatPlan edge_mats; - memset(&edge_mats, 0, sizeof edge_mats); - if (allow_live_range_split) prepare_split_edge_materialization(f, &edge_mats); - rewrite_func(f, &live); - apply_split_edge_materialization(f, &edge_mats); +void opt_regalloc_locations(Func* f, OptLiveInfo* live_out) { + opt_regalloc_place(f, live_out); } diff --git a/src/opt/pass_o2.c b/src/opt/pass_o2.c @@ -512,8 +512,8 @@ void opt_block_cloning(Func* f) { } if (changed) { - opt_analysis_invalidate(f, OPT_ANALYSIS_CFG | OPT_ANALYSIS_DEF_USE | - OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); + opt_analysis_invalidate( + f, OPT_ANALYSIS_DEF_USE | OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); opt_build_cfg(f); } opt_rebuild_def_use(f); @@ -1776,8 +1776,8 @@ void opt_gvn(Func* f) { } if (ctx.cfg_changed) { - opt_analysis_invalidate(f, OPT_ANALYSIS_CFG | OPT_ANALYSIS_DEF_USE | - OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); + opt_analysis_invalidate( + f, OPT_ANALYSIS_DEF_USE | OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); opt_build_cfg(f); gvn_realign_phi_preds(f); } else if (ctx.changed) { diff --git a/src/opt/pass_ssa.c b/src/opt/pass_ssa.c @@ -918,8 +918,8 @@ static void realign_phi_preds(Func* f) { void opt_make_conventional_ssa(Func* f) { if (!f) return; - opt_analysis_invalidate(f, OPT_ANALYSIS_CFG | OPT_ANALYSIS_DEF_USE | - OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); + opt_analysis_invalidate( + f, OPT_ANALYSIS_DEF_USE | OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); for (u32 b = 0; b < f->nblocks; ++b) { Block* bl = &f->blocks[b]; if (!bl->ninsts || (IROp)bl->insts[0].op != IR_PHI) continue; diff --git a/test/driver/run.sh b/test/driver/run.sh @@ -1077,7 +1077,7 @@ if "$KIT" cc -O1 --emit=ir -c "$work/ir.c" -o "$work/ir.out" \ grep -q "^func sym#" "$work/ir.out" && grep -q "binop" "$work/ir.out" && grep -q "iadd" "$work/ir.out" && - grep -q "ret values=\[" "$work/ir.out"; then + grep -q "ret value=" "$work/ir.out"; then ok "cc-emit-ir" else not_ok "cc-emit-ir" "$work/ir-emit.err"