kit

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

commit c0b3c960cc1669197efecd77359fd923f4cb12b1
parent 6990bd41b09bbc146d29fc51a07d07c8fe04de65
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  9 Jun 2026 22:02:57 -0700

cleanup wave D: format/identity-hook + util/ISA consolidation

Adversarial review backlog, Wave D ("use the capability hook / shared table,
don't bypass with hardcoded names / open-coded copies"). All behavior-preserving
except T1.2, which fixes a latent panic. Verified: make lib bin + test-link,
test-link-jit-tls-relax, test-rv64-tls-link, test-elf, test-macho, test-isa,
test-asm, test-aa64-inline, test-dist, test-driver-wasm, test-wasm-toy,
test-cg-api, test-toy, test-smoke-x64/rv64 — all green.

T1.2 (latent JIT panic) — the JIT tolerates two droppable undef pseudo-symbols
(Mach-O __tlv_bootstrap, COFF _tls_index) but the rule was hardcoded by memcmp
in several sites that DISAGREED: the append-resolve loop accepted only
__tlv_bootstrap, so appending a COFF object with a non-weak undef _tls_index
passed preflight then panicked, while the full-link path resolved it. Added one
arbiter obj_format_jit_undef_internal (recognizes both) and funneled all undef
sites (link_jit preflight + append-resolve, link_resolve full-link) through it;
no src/link site hardcodes the names now. Left obj_format_jit_drops_symbol_ref
(the reloc-DROP authority, _tls_index-only) unchanged — __tlv_bootstrap relocs
are rewritten, not dropped.

D.2 — replaced the hardcoded R_RV_RELAX/TPREL_ADD/ALIGN name skip in the layout
pass with reloc_kind_is_marker (reads the RELOC_MARKER descriptor flag that was
dead); gave R_RV_ALIGN its descriptor row; added reloc_kind_is_width_dyn for the
sibling R_SET/SUB_ULEB128 name check. Verified set-equality + width-change
inertness (ALIGN dropped before the width gate).

F.4 — hoisted the byte-identical ctx->diag error wrapper (target_diag / cas_diagf
/ compress_diagf / pkg_diagf) into one core/diag.h kit_ctx_diagf; routed the 4
API files through it, dropped now-unused <stdarg.h>. (Completes the B5 deferral.)

F.5 — extracted tls_emit_storage from the byte-identical tbss/tdata body in
define_tls_elf/define_tls_macho (obj_tls.c).

F.1 — routed c_target's ~6 hand-rolled growable arrays through core's VEC_GROW.

G.1 — single-sourced the AArch64 condition-code table: aa64_cond_name /
aa64_cond_from_name in isa.{h,c}; disasm/emit/asm-parse now share it.

G.3 — collapsed the 54 byte-identical wasm memarg decode arms into a
table-driven path (wasm_insn_by_byte reverse lookup); irregular arms (incl.
memory.size/grow) kept explicit.

Also: pre-existing test-cbackend failures (red since de9bface, verified in a
worktree) — added the missing /C skip for cg_native_inline_asm_machine_constraints
(inherent: C backend has no machine register model), and documented the
pre-existing memory_grow_large/C segfault (a c_target memory.grow bug) without
hiding it. See doc/plan/CLEANUP-2026-06-09.md.

Diffstat:
Mdoc/plan/CLEANUP-2026-06-09.md | 33++++++++++++++++++++++++++-------
Msrc/api/cas.c | 56++++++++++++++++++++------------------------------------
Msrc/api/compress.c | 23++++-------------------
Msrc/api/core.c | 21++++-----------------
Msrc/api/package.c | 129++++++++++++++++++++++++++++++++++++-------------------------------------------
Msrc/arch/aa64/asm.c | 37+------------------------------------
Msrc/arch/aa64/disasm.c | 7+------
Msrc/arch/aa64/isa.c | 38++++++++++++++++++++++++++++++++++----
Msrc/arch/aa64/isa.h | 9+++++++++
Msrc/arch/c_target/c_emit.c | 164++++++++++++++++++++++++++++++++++---------------------------------------------
Msrc/arch/riscv/reloc.c | 10++++++----
Msrc/core/diag.c | 8++++++++
Msrc/core/diag.h | 5+++++
Msrc/link/link_jit.c | 39++++++++++++++++++++-------------------
Msrc/link/link_reloc_desc.h | 15+++++++++++++++
Msrc/link/link_reloc_layout.c | 7++-----
Msrc/link/link_resolve.c | 43+++++++++++++++----------------------------
Msrc/obj/obj.h | 9+++++++++
Msrc/obj/obj_tls.c | 80+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/wasm/decode.c | 344+++++++++----------------------------------------------------------------------
Msrc/wasm/wasm_insn_table.c | 9+++++++++
Msrc/wasm/wasm_insn_table.h | 14++++++++++----
Mtest/parse/run.sh | 7+++++++
23 files changed, 420 insertions(+), 687 deletions(-)

diff --git a/doc/plan/CLEANUP-2026-06-09.md b/doc/plan/CLEANUP-2026-06-09.md @@ -32,7 +32,7 @@ Dormant or narrow today, but real defects. `slen-2+1 > cap` guard (`:731`); unbounded `memcpy` into `char path[4096]`. *Fix:* extract a shared `header_name_to_path(slice, out, cap, *system)` with the cap check; call from both `parse_include_path` and `do_embed`. -- [ ] **T1.2 JIT panic on `_tls_index`** — `src/link/link_jit.c:791-811, 991-1005`. Append +- [x] **T1.2 JIT panic on `_tls_index`** — `src/link/link_jit.c:791-811, 991-1005`. Append preflight accepts undef `__tlv_bootstrap` *and* `_tls_index`; the append-resolve loop only special-cases `__tlv_bootstrap`, so a COFF object referencing `_tls_index` passes preflight then panics. The full-link path resolves it — the copies drifted. @@ -145,7 +145,7 @@ confirmed finding. ### D. Leaky format/arch identity in generic code (med→low, 7) - [ ] **D.1** JIT undef-resolution memcmps `__tlv_bootstrap`/`_tls_index` in 3+ sites — covered by **T1.2** (extend the format authority to cover both pseudo-symbols). -- [ ] **D.2** `link_emit_relocations` skips RV markers *by enum name* while the +- [x] **D.2** `link_emit_relocations` skips RV markers *by enum name* while the `RELOC_MARKER` descriptor flag built for it sits dead — `src/link/link_reloc_layout.c:937`. *Fix:* add `reloc_kind_is_marker` (reads `RELOC_MARKER`), tag `R_RV_ALIGN`'s row, replace the name check; same for the dead `RELOC_WIDTH_DYN` at `:982`. @@ -174,7 +174,7 @@ confirmed finding. *Fix:* shared driver helpers. ### F. Hand-rolled utils reimplement shared ones (low, 14) -- [ ] **F.1** `VEC_GROW`/`vec_grow_` bypassed: ~6× in `c_target` (`c_emit.c:133-144,374-392, +- [x] **F.1** `VEC_GROW`/`vec_grow_` bypassed: ~6× in `c_target` (`c_emit.c:133-144,374-392, 1006-1017,1541-1553,1704-1721,1723-1739,2364-2375`, incl. 2 byte-identical `sym_forwarded` bitmaps), 5× in emu/interp, 4-6× in cpp. *Fix:* route through `VEC_GROW` (+ a zeroing wrapper for the u8 bitmaps). @@ -182,19 +182,19 @@ confirmed finding. `strtab_add` 4× in the ELF linker. *Fix:* use the shared builder. - [ ] **F.3** LEB128 encoders forked in `debug_emit`; `dw_skip_die_attrs` loop inlined 6×. *Fix:* one encoder + one skip helper. -- [~] **F.4** path-join triplicated (2 copies in one dist file); `ctx->diag` varargs wrapper +- [x] **F.4** path-join triplicated (2 copies in one dist file); `ctx->diag` varargs wrapper copied across 4 API files; `run.c` reimplements `driver_parse_u64`/`driver_record_mcmodel`. *Fix:* use the shared helpers. -- [ ] **F.5** TLS storage emission byte-identical between `define_tls_elf`/`define_tls_macho` +- [x] **F.5** TLS storage emission byte-identical between `define_tls_elf`/`define_tls_macho` (`src/obj/obj_tls.c:78-191`). *Fix:* extract `tls_emit_storage(...)`. ### G. ISA/encoding tables duplicated outside their authority (low, 4) -- [ ] **G.1** aa64 condition-code table duplicated 4× (`disasm.c:38-41`, `isa.c`); no +- [x] **G.1** aa64 condition-code table duplicated 4× (`disasm.c:38-41`, `isa.c`); no exported `aa64_cond_*`. *Fix:* one canonical `aa64_cond_name`/`aa64_cond_from_name` in `isa.{h,c}`; route disasm/emit/parse through it. - [ ] **G.2** aa64 `dbg.c` re-hardcodes B/B.cond/CBZ/ADR masks `isa.h` exports, inconsistently within one function (`dbg.c:94-179`). *Fix:* use the named `AA64_*_FAMILY_MASK/MATCH`. -- [ ] **G.3** 54 byte-identical wasm memarg decode arms maintain a private byte→kind map +- [x] **G.3** 54 byte-identical wasm memarg decode arms maintain a private byte→kind map (`src/wasm/decode.c:196-991`). *Fix:* reverse lookup into `wasm_insn_table`; collapse the contiguous ranges. - [ ] **G.4** RV disassembler dispatches sub-format by `strcmp` on the display string. @@ -316,6 +316,25 @@ build + targeted tests + commit. Baseline captured in `build/baseline_summary.tx --- +## Pre-existing issues surfaced during cleanup (NOT regressions) + +`make test-cbackend` (the `--emit=c` C-source backend suite) was **already red at the +pre-cleanup baseline `de9bface`** — verified by building that commit in a throwaway +worktree. Two failing cases, neither caused by this cleanup: + +- **`cg_native_inline_asm_machine_constraints/O0/C`** — inherent: the C backend replays IR + as portable C with no arch register model, so `cg/asm.c` correctly rejects machine register + constraints (aa64 `"x"`/`"y"`, rv64 `"cr"`/`"cf"`). A native-only feature test that lacked a + /C skip. **FIXED** by adding the matching /C skip in `test/parse/run.sh`. +- **`memory_grow_large/C`** — `expected 42 got 139` (SIGSEGV): a grown-range access + (`memory 1 300`, grow to 300 pages, store/load at ~18.7 MB) segfaults under the C backend — + a genuine pre-existing c_target `memory.grow` bug (backing store likely not realloc'd to the + grown page count). **NOT fixed and NOT skipped** — hiding a real bug is wrong; left failing + and flagged for a dedicated c_target memory-model investigation. (Confirmed failing + identically at `de9bface`.) + +--- + ## Appendix A — all 135 confirmed findings by subsystem ### aa64-asm-isa — 4 diff --git a/src/api/cas.c b/src/api/cas.c @@ -4,7 +4,6 @@ #include "dist/cas.h" #include <kit/cas.h> -#include <stdarg.h> #include <stdio.h> #include <string.h> @@ -13,21 +12,6 @@ #include "dist/dist.h" #include "dist/tree.h" -/* Emit a human-readable operational error through the context diag sink (no - * source location), mirroring how other subsystems report. No-op when the - * caller supplied no sink. */ -static void cas_diagf(const KitContext* ctx, const char* fmt, ...) { - va_list ap; - KitSrcLoc loc; - if (!ctx || !ctx->diag) return; - loc.file_id = 0; - loc.line = 0; - loc.col = 0; - va_start(ap, fmt); - diag_emitv(ctx->diag, KIT_DIAG_ERROR, loc, fmt, ap); - va_end(ap); -} - struct KitCas { /* Own a copy of the context so the handle outlives the caller's (possibly * stack-local) KitContext; ctx points at the stored copy. The pointed-to @@ -94,11 +78,11 @@ KitStatus kit_cas_add_blob(KitCas* cas, const uint8_t* data, size_t len, DistBlobInfo bi; if (!cas || !out) return KIT_INVALID; if (dist_blob_info(&bi, data, len, DIST_BLOB_CHUNK_SIZE_DEFAULT) != DIST_OK) { - cas_diagf(cas->ctx, "failed to hash blob"); + kit_ctx_diagf(cas->ctx, "failed to hash blob"); return KIT_ERR; } if (dist_cas_put_blob(&cas->dist, bi.id, data, len) != DIST_OK) { - cas_diagf(cas->ctx, "failed to store blob"); + kit_ctx_diagf(cas->ctx, "failed to store blob"); return KIT_IO; } memcpy(out->id, bi.id, DIST_BLAKE2B_LEN); @@ -139,23 +123,23 @@ KitStatus kit_cas_tree_builder_add(KitCasTreeBuilder* b, const char* tree_path, if (!b || !tree_path) return KIT_INVALID; cas = b->cas; if (b->tree.n_entries >= b->tree.cap_entries) { - cas_diagf(cas->ctx, "too many tree entries"); + kit_ctx_diagf(cas->ctx, "too many tree entries"); return KIT_ERR; } if (!dist_tree_path_valid(tree_path)) { - cas_diagf(cas->ctx, "unsafe tree path: %s", tree_path); + kit_ctx_diagf(cas->ctx, "unsafe tree path: %s", tree_path); return KIT_INVALID; } if (!dist_tree_mode_name((uint8_t)mode)) { - cas_diagf(cas->ctx, "bad tree mode for: %s", tree_path); + kit_ctx_diagf(cas->ctx, "bad tree mode for: %s", tree_path); return KIT_INVALID; } if (dist_blob_info(&bi, data, len, DIST_BLOB_CHUNK_SIZE_DEFAULT) != DIST_OK) { - cas_diagf(cas->ctx, "failed to hash blob: %s", tree_path); + kit_ctx_diagf(cas->ctx, "failed to hash blob: %s", tree_path); return KIT_ERR; } if (dist_cas_put_blob(&cas->dist, bi.id, data, len) != DIST_OK) { - cas_diagf(cas->ctx, "failed to store blob: %s", tree_path); + kit_ctx_diagf(cas->ctx, "failed to store blob: %s", tree_path); return KIT_IO; } e = &b->tree.entries[b->tree.n_entries++]; @@ -178,24 +162,24 @@ KitStatus kit_cas_tree_builder_finish(KitCasTreeBuilder* b, if (!b || !out_tree_id) return KIT_INVALID; cas = b->cas; if (dist_tree_sort_validate(&b->tree, err, sizeof err) != DIST_OK) { - cas_diagf(cas->ctx, "%s", err); + kit_ctx_diagf(cas->ctx, "%s", err); return KIT_MALFORMED; } if (kit_writer_mem(cas->ctx->heap, &w) != KIT_OK) { - cas_diagf(cas->ctx, "failed to allocate tree writer"); + kit_ctx_diagf(cas->ctx, "failed to allocate tree writer"); return KIT_NOMEM; } if (dist_tree_emit(&b->tree, w) != DIST_OK || kit_writer_status(w) != KIT_OK) { kit_writer_close(w); - cas_diagf(cas->ctx, "failed to emit tree manifest"); + kit_ctx_diagf(cas->ctx, "failed to emit tree manifest"); return KIT_ERR; } bytes = kit_writer_mem_bytes(w, &len); dist_tree_id(out_tree_id, bytes, len); if (dist_cas_put_tree(&cas->dist, out_tree_id, bytes, len) != DIST_OK) { kit_writer_close(w); - cas_diagf(cas->ctx, "failed to store tree manifest"); + kit_ctx_diagf(cas->ctx, "failed to store tree manifest"); return KIT_IO; } kit_writer_close(w); @@ -226,7 +210,7 @@ static int cas_dir_walk_file(void* user, const char* source_path, fd.size = 0; fd.token = NULL; if (io->read_all(io->user, source_path, &fd) != KIT_OK) { - cas_diagf(cas->ctx, "failed to read: %s", source_path); + kit_ctx_diagf(cas->ctx, "failed to read: %s", source_path); w->status = KIT_IO; return 1; } @@ -255,7 +239,7 @@ KitStatus kit_cas_add_tree_from_dir(KitCas* cas, const char* root, if (cas->host.walk_regular_files(cas->host.user, root, cas_dir_walk_file, &w) != 0) { if (w.status == KIT_OK) { - cas_diagf(cas->ctx, "failed to walk directory: %s", root); + kit_ctx_diagf(cas->ctx, "failed to walk directory: %s", root); w.status = KIT_IO; } kit_cas_tree_builder_free(b); @@ -285,13 +269,13 @@ static KitStatus cas_load_tree(KitCas* cas, raw->size = 0; raw->token = NULL; if (dist_cas_get_tree(&cas->dist, tree_id, raw) != DIST_OK) { - cas_diagf(cas->ctx, "failed to load tree"); + kit_ctx_diagf(cas->ctx, "failed to load tree"); h->free(h, *entries, DIST_MAX_FILES * sizeof **entries); *entries = NULL; return KIT_NOT_FOUND; } if (dist_tree_parse(raw->data, raw->size, tree, err, sizeof err) != DIST_OK) { - cas_diagf(cas->ctx, "%s", err); + kit_ctx_diagf(cas->ctx, "%s", err); if (cas->host.file_io->release) cas->host.file_io->release(cas->host.file_io->user, raw); h->free(h, *entries, DIST_MAX_FILES * sizeof **entries); @@ -318,13 +302,13 @@ KitStatus kit_cas_inspect_tree(KitCas* cas, raw.size = 0; raw.token = NULL; if (dist_cas_get_tree(&cas->dist, tree_id, &raw) != DIST_OK) { - cas_diagf(cas->ctx, "failed to load tree"); + kit_ctx_diagf(cas->ctx, "failed to load tree"); return KIT_NOT_FOUND; } if (raw.size && kit_writer_write(out, raw.data, raw.size) != KIT_OK) { if (cas->host.file_io->release) cas->host.file_io->release(cas->host.file_io->user, &raw); - cas_diagf(cas->ctx, "failed to write tree manifest"); + kit_ctx_diagf(cas->ctx, "failed to write tree manifest"); return KIT_IO; } if (cas->host.file_io->release) @@ -351,7 +335,7 @@ KitStatus kit_cas_verify_tree(KitCas* cas, fd.size = 0; fd.token = NULL; if (dist_cas_get_blob(&cas->dist, e->blob, &fd) != DIST_OK) { - cas_diagf(cas->ctx, "missing or corrupt blob for: %s", e->path); + kit_ctx_diagf(cas->ctx, "missing or corrupt blob for: %s", e->path); st = KIT_NOT_FOUND; break; } @@ -360,7 +344,7 @@ KitStatus kit_cas_verify_tree(KitCas* cas, bi.size != e->size || memcmp(bi.root, e->root, DIST_BLAKE2B_LEN) != 0) { if (cas->host.file_io->release) cas->host.file_io->release(cas->host.file_io->user, &fd); - cas_diagf(cas->ctx, "blob root mismatch for: %s", e->path); + kit_ctx_diagf(cas->ctx, "blob root mismatch for: %s", e->path); st = KIT_INVALID; break; } @@ -382,7 +366,7 @@ KitStatus kit_cas_materialize_tree(KitCas* cas, st = cas_load_tree(cas, tree_id, &tree, &entries, &raw); if (st != KIT_OK) return st; if (dist_cas_materialize_tree(&cas->dist, &tree, dst) != DIST_OK) { - cas_diagf(cas->ctx, "failed to materialize tree"); + kit_ctx_diagf(cas->ctx, "failed to materialize tree"); st = KIT_ERR; } cas_free_tree(cas, entries, &raw); diff --git a/src/api/compress.c b/src/api/compress.c @@ -3,7 +3,6 @@ * <kit/compress.h>. */ #include <kit/compress.h> -#include <stdarg.h> #include "core/diag.h" #include "dist/deflate.h" @@ -15,20 +14,6 @@ static const uint8_t COMPRESS_GZIP_MAGIC[2] = {0x1f, 0x8b}; static const uint8_t COMPRESS_LZ4F_MAGIC[4] = {0x04, 0x22, 0x4d, 0x18}; -/* Emit an operational error through the context diag sink (no source location), - * mirroring src/api/cas.c. No-op when the caller supplied no sink. */ -static void compress_diagf(const KitContext* ctx, const char* fmt, ...) { - va_list ap; - KitSrcLoc loc; - if (!ctx || !ctx->diag) return; - loc.file_id = 0; - loc.line = 0; - loc.col = 0; - va_start(ap, fmt); - diag_emitv(ctx->diag, KIT_DIAG_ERROR, loc, fmt, ap); - va_end(ap); -} - static const char* compress_fmt_name(KitCompressFormat fmt) { return fmt == KIT_COMPRESS_GZIP ? "gzip" : "lz4"; } @@ -45,11 +30,11 @@ KitStatus kit_compress(const KitContext* ctx, KitCompressFormat fmt, r = dist_lz4f_compress(ctx->heap, out, data, len); break; default: - compress_diagf(ctx, "compress: unknown format %d", (int)fmt); + kit_ctx_diagf(ctx, "compress: unknown format %d", (int)fmt); return KIT_ERR; } if (r != DIST_OK) { - compress_diagf(ctx, "compress: %s encode failed", compress_fmt_name(fmt)); + kit_ctx_diagf(ctx, "compress: %s encode failed", compress_fmt_name(fmt)); return KIT_ERR; } return KIT_OK; @@ -67,11 +52,11 @@ KitStatus kit_decompress(const KitContext* ctx, KitCompressFormat fmt, r = dist_lz4f_decompress(ctx->heap, out, data, len); break; default: - compress_diagf(ctx, "decompress: unknown format %d", (int)fmt); + kit_ctx_diagf(ctx, "decompress: unknown format %d", (int)fmt); return KIT_ERR; } if (r != DIST_OK) { - compress_diagf(ctx, "decompress: %s decode failed", compress_fmt_name(fmt)); + kit_ctx_diagf(ctx, "decompress: %s decode failed", compress_fmt_name(fmt)); return KIT_ERR; } return KIT_OK; diff --git a/src/api/core.c b/src/api/core.c @@ -3,7 +3,6 @@ #include "core/core.h" #include <kit/core.h> -#include <stdarg.h> #include <string.h> #include "arch/arch.h" @@ -12,18 +11,6 @@ #include "core/pool.h" #include "core/slice.h" -static void target_diag(const KitContext* ctx, const char* fmt, ...) { - va_list ap; - KitSrcLoc loc; - if (!ctx || !ctx->diag) return; - loc.file_id = 0; - loc.line = 0; - loc.col = 0; - va_start(ap, fmt); - diag_emitv(ctx->diag, KIT_DIAG_ERROR, loc, fmt, ap); - va_end(ap); -} - static void bitset_set(u64* words, u32 nwords, u32 idx, int enabled) { u32 w = idx / 64u; u64 bit = 1ull << (idx % 64u); @@ -53,7 +40,7 @@ KitStatus kit_target_new(const KitContext* ctx, const KitTargetOptions* opts, if (!ctx || !ctx->heap || !opts) return KIT_INVALID; arch = arch_lookup(opts->spec.arch); if (!arch) { - target_diag(ctx, "unsupported target architecture: %u", + kit_ctx_diagf(ctx, "unsupported target architecture: %u", (unsigned)opts->spec.arch); return KIT_UNSUPPORTED; } @@ -105,7 +92,7 @@ KitStatus kit_target_new(const KitContext* ctx, const KitTargetOptions* opts, KitStatus st = arch_target_feature_apply_isa( arch, &t->spec, opts->isa, t->feature_words, t->nfeature_words); if (st != KIT_OK) { - target_diag(ctx, "unsupported ISA/profile for %s: %.*s", arch->name, + kit_ctx_diagf(ctx, "unsupported ISA/profile for %s: %.*s", arch->name, KIT_SLICE_ARG(opts->isa)); kit_target_free(t); return st == KIT_UNSUPPORTED ? KIT_INVALID : st; @@ -116,7 +103,7 @@ KitStatus kit_target_new(const KitContext* ctx, const KitTargetOptions* opts, u32 idx; KitSlice name = opts->features[i].name; if (!arch_target_feature_index(arch, name, &idx)) { - target_diag(ctx, "unknown target feature for %s: %.*s", arch->name, + kit_ctx_diagf(ctx, "unknown target feature for %s: %.*s", arch->name, KIT_SLICE_ARG(name)); kit_target_free(t); return KIT_INVALID; @@ -135,7 +122,7 @@ KitStatus kit_target_new(const KitContext* ctx, const KitTargetOptions* opts, if (arch_resolve_float_abi(arch, &t->spec, t->feature_words, t->nfeature_words, opts->abi, errbuf, sizeof errbuf) == KIT_INVALID) { - target_diag(ctx, "%s", errbuf); + kit_ctx_diagf(ctx, "%s", errbuf); kit_target_free(t); return KIT_INVALID; } diff --git a/src/api/package.c b/src/api/package.c @@ -4,12 +4,11 @@ * * The pipelines were lifted from the kit pkg tool; the driver keeps only * argument parsing, stdout formatting, host-vtable wiring, and trusted-keys - * path/pin policy. Operational errors emit through ctx->diag (pkg_diagf); + * path/pin policy. Operational errors emit through ctx->diag (kit_ctx_diagf); * arg/CLI errors stay in the driver. */ #include <kit/cas.h> #include <kit/package.h> -#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -101,18 +100,6 @@ typedef struct PkgLoadedTree { size_t size; } PkgLoadedTree; -static void pkg_diagf(const KitContext* ctx, const char* fmt, ...) { - va_list ap; - KitSrcLoc loc; - if (!ctx || !ctx->diag) return; - loc.file_id = 0; - loc.line = 0; - loc.col = 0; - va_start(ap, fmt); - diag_emitv(ctx->diag, KIT_DIAG_ERROR, loc, fmt, ap); - va_end(ap); -} - /* ---------------------------------------------------------------------- */ /* shared helpers */ /* ---------------------------------------------------------------------- */ @@ -122,14 +109,14 @@ static int pkg_write_file(const KitContext* ctx, const char* path, KitWriter* w = NULL; int rc; if (ctx->file_io->open_writer(ctx->file_io->user, path, &w) != KIT_OK) { - pkg_diagf(ctx, "failed to open output: %s", path); + kit_ctx_diagf(ctx, "failed to open output: %s", path); return DIST_ERR; } rc = (len == 0 || kit_writer_write(w, data, len) == KIT_OK) ? DIST_OK : DIST_ERR; if (kit_writer_status(w) != KIT_OK) rc = DIST_ERR; kit_writer_close(w); - if (rc != DIST_OK) pkg_diagf(ctx, "failed to write: %s", path); + if (rc != DIST_OK) kit_ctx_diagf(ctx, "failed to write: %s", path); return rc; } @@ -333,24 +320,24 @@ static int pkg_source_add_entry(PkgSource* src, const char* tree_path, DistBlobInfo bi; DistTreeEntry* e; if (src->tree.n_entries >= src->tree.cap_entries) { - pkg_diagf(src->ctx, "create: too many tree entries"); + kit_ctx_diagf(src->ctx, "create: too many tree entries"); return DIST_ERR; } if (!dist_tree_path_valid(tree_path)) { - pkg_diagf(src->ctx, "create: unsafe tree path: %s", tree_path); + kit_ctx_diagf(src->ctx, "create: unsafe tree path: %s", tree_path); return DIST_ERR; } if (!dist_tree_mode_name(mode)) { - pkg_diagf(src->ctx, "create: bad tree mode: %s", tree_path); + kit_ctx_diagf(src->ctx, "create: bad tree mode: %s", tree_path); return DIST_ERR; } if (dist_blob_info(&bi, fd->data, fd->size, DIST_BLOB_CHUNK_SIZE_DEFAULT) != DIST_OK) { - pkg_diagf(src->ctx, "create: failed to hash blob: %s", tree_path); + kit_ctx_diagf(src->ctx, "create: failed to hash blob: %s", tree_path); return DIST_ERR; } if (pkg_source_store_blob(src, fd, &bi, take_fd) != DIST_OK) { - pkg_diagf(src->ctx, "create: failed to store blob metadata: %s", tree_path); + kit_ctx_diagf(src->ctx, "create: failed to store blob metadata: %s", tree_path); return DIST_ERR; } e = &src->tree.entries[src->tree.n_entries++]; @@ -373,7 +360,7 @@ static int pkg_source_walk_file(void* user, const char* source_path, fd.size = 0; fd.token = NULL; if (io->read_all(io->user, source_path, &fd) != KIT_OK) { - pkg_diagf(src->ctx, "create: cannot read file: %s", source_path); + kit_ctx_diagf(src->ctx, "create: cannot read file: %s", source_path); return 1; } rc = pkg_source_add_entry( @@ -386,13 +373,13 @@ static int pkg_source_walk_file(void* user, const char* source_path, static int pkg_source_finish_tree(PkgSource* src) { char err[128]; if (dist_tree_sort_validate(&src->tree, err, sizeof err) != DIST_OK) { - pkg_diagf(src->ctx, "create: %s", err); + kit_ctx_diagf(src->ctx, "create: %s", err); return DIST_ERR; } if (kit_writer_mem(src->ctx->heap, &src->tree_mem) != KIT_OK) return DIST_ERR; if (dist_tree_emit(&src->tree, src->tree_mem) != DIST_OK || kit_writer_status(src->tree_mem) != KIT_OK) { - pkg_diagf(src->ctx, "create: failed to emit tree manifest"); + kit_ctx_diagf(src->ctx, "create: failed to emit tree manifest"); return DIST_ERR; } src->tree_bytes = kit_writer_mem_bytes(src->tree_mem, &src->tree_size); @@ -404,7 +391,7 @@ static int pkg_source_finish_tree(PkgSource* src) { static int pkg_source_from_root(PkgSource* src, const char* root) { if (src->host->walk_regular_files(src->host->user, root, pkg_source_walk_file, src) != 0) { - pkg_diagf(src->ctx, "create: failed to walk directory: %s", root); + kit_ctx_diagf(src->ctx, "create: failed to walk directory: %s", root); return DIST_ERR; } return pkg_source_finish_tree(src); @@ -427,7 +414,7 @@ static int pkg_source_load_blob_from_cas(PkgSource* src, DistCas* cas, if (existing) { if (existing->size != e->size || memcmp(existing->root, e->root, DIST_BLAKE2B_LEN) != 0) { - pkg_diagf(src->ctx, "create: duplicate blob metadata mismatch: %s", + kit_ctx_diagf(src->ctx, "create: duplicate blob metadata mismatch: %s", e->path); return DIST_ERR; } @@ -437,14 +424,14 @@ static int pkg_source_load_blob_from_cas(PkgSource* src, DistCas* cas, fd.size = 0; fd.token = NULL; if (dist_cas_get_blob(cas, e->blob, &fd) != DIST_OK) { - pkg_diagf(src->ctx, "create: missing or corrupt blob for: %s", e->path); + kit_ctx_diagf(src->ctx, "create: missing or corrupt blob for: %s", e->path); return DIST_ERR; } if (dist_blob_info(&bi, fd.data, fd.size, DIST_BLOB_CHUNK_SIZE_DEFAULT) != DIST_OK || bi.size != e->size || memcmp(bi.root, e->root, DIST_BLAKE2B_LEN) != 0) { if (io->release) io->release(io->user, &fd); - pkg_diagf(src->ctx, "create: blob root mismatch for: %s", e->path); + kit_ctx_diagf(src->ctx, "create: blob root mismatch for: %s", e->path); return DIST_ERR; } if (pkg_source_store_blob(src, &fd, &bi, 1) != DIST_OK) { @@ -460,7 +447,7 @@ static int pkg_source_from_cas(PkgSource* src, const char* cas_dir, char err[128]; size_t i; if (pkg_parse_id(tree_s, src->tree_id) != DIST_OK) { - pkg_diagf(src->ctx, "create: bad tree id: %s", tree_s); + kit_ctx_diagf(src->ctx, "create: bad tree id: %s", tree_s); return DIST_ERR; } pkg_cas_init_get(&cas, src->host, cas_dir); @@ -468,7 +455,7 @@ static int pkg_source_from_cas(PkgSource* src, const char* cas_dir, src->tree_fd.size = 0; src->tree_fd.token = NULL; if (dist_cas_get_tree(&cas, src->tree_id, &src->tree_fd) != DIST_OK) { - pkg_diagf(src->ctx, "create: missing or corrupt tree: %s", tree_s); + kit_ctx_diagf(src->ctx, "create: missing or corrupt tree: %s", tree_s); return DIST_ERR; } src->tree_loaded = 1; @@ -476,7 +463,7 @@ static int pkg_source_from_cas(PkgSource* src, const char* cas_dir, src->tree_size = src->tree_fd.size; if (dist_tree_parse(src->tree_bytes, src->tree_size, &src->tree, err, sizeof err) != DIST_OK) { - pkg_diagf(src->ctx, "create: tree: %s", err); + kit_ctx_diagf(src->ctx, "create: tree: %s", err); return DIST_ERR; } for (i = 0; i < src->tree.n_entries; ++i) { @@ -612,7 +599,7 @@ static int pkg_build_native_regions(const KitCasHost* host, size_t stored_len = 0; if (dist_lz4_compress_block(tmp, sizeof tmp, &stored_len, raw, raw_len) != DIST_OK) { - pkg_diagf(ctx, "create: lz4-block-v1 compression failed"); + kit_ctx_diagf(ctx, "create: lz4-block-v1 compression failed"); goto fail; } r.stored_size = stored_len; @@ -669,7 +656,7 @@ static int pkg_create_kpkg(const KitCasHost* host, const KitContext* ctx, tree_url[0] = '\0'; index_url[0] = '\0'; if (shape != PKG_NATIVE_FAT && !external_dir) { - pkg_diagf(ctx, + kit_ctx_diagf(ctx, "create: --external DIR is required for non-fat native packages"); goto done; } @@ -848,7 +835,7 @@ KitStatus kit_pkg_create(const KitContext* ctx, const KitCasHost* host, } if (pkg_manifest_from_source(opts->name, opts->version, opts->description, &src, &m) != DIST_OK) { - pkg_diagf(ctx, "create: failed to build package manifest"); + kit_ctx_diagf(ctx, "create: failed to build package manifest"); goto done; } @@ -902,7 +889,7 @@ static int pkg_resolve_key(const KitContext* ctx, if (dist_minisig_parse_pubkey(opts->pubkey_bytes, opts->pubkey_len, pk, kid_chk) != DIST_OK || memcmp(kid_chk, keyid, DIST_KEYID_LEN) != 0) { - pkg_diagf(ctx, "public key id does not match signature"); + kit_ctx_diagf(ctx, "public key id does not match signature"); return DIST_ERR; } return DIST_OK; @@ -914,14 +901,14 @@ static int pkg_resolve_key(const KitContext* ctx, if (!opts->tofu) { char hex[2 * DIST_KEYID_LEN + 1]; dist_hex_encode(hex, keyid, DIST_KEYID_LEN); - pkg_diagf(ctx, "untrusted signer (key id %s)", hex); + kit_ctx_diagf(ctx, "untrusted signer (key id %s)", hex); return DIST_ERR; } if (!bundled_pub || bundled_pub_size == 0 || dist_minisig_parse_pubkey(bundled_pub, bundled_pub_size, pk, kid_chk) != DIST_OK || memcmp(kid_chk, keyid, DIST_KEYID_LEN) != 0) { - pkg_diagf(ctx, "--tofu: bundled public key is missing or mismatched"); + kit_ctx_diagf(ctx, "--tofu: bundled public key is missing or mismatched"); return DIST_ERR; } *tofu_pin = 1; @@ -937,7 +924,7 @@ static int pkg_verify_manifest(const KitContext* ctx, const uint8_t* man, const char* pidp; memset(out, 0, sizeof *out); if (dist_minisig_sig_keyid(sig, sig_len, out->keyid) != DIST_OK) { - pkg_diagf(ctx, "malformed signature"); + kit_ctx_diagf(ctx, "malformed signature"); return DIST_ERR; } if (pkg_resolve_key(ctx, out->keyid, pub, pub_len, opts, out->pk, @@ -945,19 +932,19 @@ static int pkg_verify_manifest(const KitContext* ctx, const uint8_t* man, return DIST_ERR; if (dist_minisig_verify(sig, sig_len, man, man_len, out->pk, out->trusted, sizeof out->trusted) != DIST_OK) { - pkg_diagf(ctx, "signature verification FAILED"); + kit_ctx_diagf(ctx, "signature verification FAILED"); return DIST_ERR; } pkg_hash(out->package_id, man, man_len); dist_hex_encode(pkgid_hex, out->package_id, DIST_BLAKE2B_LEN); pidp = strstr(out->trusted, "pkgid="); if (!pidp || strncmp(pidp + 6, pkgid_hex, 2 * DIST_BLAKE2B_LEN) != 0) { - pkg_diagf(ctx, "trusted comment does not match package id"); + kit_ctx_diagf(ctx, "trusted comment does not match package id"); return DIST_ERR; } if (dist_package_manifest_parse(man, man_len, &out->manifest, err, sizeof err) != DIST_OK) { - pkg_diagf(ctx, "manifest: %s", err); + kit_ctx_diagf(ctx, "manifest: %s", err); return DIST_ERR; } return DIST_OK; @@ -980,13 +967,13 @@ static int pkg_parse_tree_object(const KitContext* ctx, PkgLoadedTree* out, memset(out, 0, sizeof *out); dist_tree_id(got, data, len); if (memcmp(got, id, DIST_BLAKE2B_LEN) != 0) { - pkg_diagf(ctx, "tree id mismatch: %s", label); + kit_ctx_diagf(ctx, "tree id mismatch: %s", label); return DIST_ERR; } out->tree.entries = out->entries; out->tree.cap_entries = DIST_MAX_FILES; if (dist_tree_parse(data, len, &out->tree, err, sizeof err) != DIST_OK) { - pkg_diagf(ctx, "tree: %s", err); + kit_ctx_diagf(ctx, "tree: %s", err); return DIST_ERR; } memcpy(out->id, id, DIST_BLAKE2B_LEN); @@ -1004,7 +991,7 @@ static int pkg_verify_artifact_overlays(const KitContext* ctx, const DistPackageArtifact* a = &m->artifacts[i]; if (a->output_id != out->id) continue; if (!dist_tree_find(tree, a->path)) { - pkg_diagf(ctx, "artifact path not in output tree: %s", a->path); + kit_ctx_diagf(ctx, "artifact path not in output tree: %s", a->path); return DIST_ERR; } } @@ -1016,7 +1003,7 @@ static int pkg_write_output_file(const KitCasHost* host, const KitContext* ctx, const uint8_t* data, size_t len) { char full[PKG_PATH_BUF], parent[PKG_PATH_BUF]; if (pkg_join_path(full, sizeof full, out_dir, e->path) != DIST_OK) { - pkg_diagf(ctx, "output path too long: %s", e->path); + kit_ctx_diagf(ctx, "output path too long: %s", e->path); return DIST_ERR; } pkg_parent_dir(full, parent, sizeof parent); @@ -1061,7 +1048,7 @@ static int pkg_verify_portable_tree(const KitCasHost* host, PkgLoadedTree tree; size_t i; if (!te) { - pkg_diagf(ctx, "portable package missing tree object"); + kit_ctx_diagf(ctx, "portable package missing tree object"); return DIST_ERR; } if (pkg_parse_tree_object(ctx, &tree, out->tree, te->data, te->size, @@ -1075,11 +1062,11 @@ static int pkg_verify_portable_tree(const KitCasHost* host, const DistTarEntry* be = pkg_portable_find_cas(entries, ne, "blob", e->blob); if (!be) { - pkg_diagf(ctx, "portable package missing blob: %s", e->path); + kit_ctx_diagf(ctx, "portable package missing blob: %s", e->path); return DIST_ERR; } if (pkg_verify_blob_bytes(e, be->data, be->size) != DIST_OK) { - pkg_diagf(ctx, "blob hash mismatch: %s", e->path); + kit_ctx_diagf(ctx, "blob hash mismatch: %s", e->path); return DIST_ERR; } if (out_dir && pkg_write_output_file(host, ctx, out_dir, e, be->data, @@ -1097,13 +1084,13 @@ static int pkg_load_portable(const KitContext* ctx, const uint8_t* data, size_t ilen; inflated = pkg_mem(ctx); if (!inflated || dist_gz_decompress(inflated, data, len) != DIST_OK) { - pkg_diagf(ctx, "malformed portable package"); + kit_ctx_diagf(ctx, "malformed portable package"); if (inflated) kit_writer_close(inflated); return DIST_ERR; } bytes = kit_writer_mem_bytes(inflated, &ilen); if (dist_tar_iter(bytes, ilen, entries, PKG_MAX_TAR_ENTRIES, ne) != DIST_OK) { - pkg_diagf(ctx, "malformed portable tar"); + kit_ctx_diagf(ctx, "malformed portable tar"); kit_writer_close(inflated); return DIST_ERR; } @@ -1127,7 +1114,7 @@ static int pkg_verify_portable(const KitContext* ctx, const KitCasHost* host, sig = pkg_find_name(entries, ne, PKG_META_SIG); pub = pkg_find_name(entries, ne, PKG_META_PUB); if (!man || !sig) { - pkg_diagf(ctx, "package missing manifest or signature"); + kit_ctx_diagf(ctx, "package missing manifest or signature"); goto done; } if (pkg_verify_manifest(ctx, man->data, man->size, sig->data, sig->size, @@ -1293,7 +1280,7 @@ static int pkg_native_load_tree(const KitContext* ctx, const uint8_t* data, char rel[PKG_PATH_BUF]; int rc; if (!obj || !external_dir) { - pkg_diagf(ctx, "external tree object is missing"); + kit_ctx_diagf(ctx, "external tree object is missing"); return DIST_ERR; } if (obj->url[0]) @@ -1305,14 +1292,14 @@ static int pkg_native_load_tree(const KitContext* ctx, const uint8_t* data, fd.size = 0; fd.token = NULL; if (pkg_read_external_file(ctx, external_dir, rel, &fd) != DIST_OK) { - pkg_diagf(ctx, "missing external tree object: %s", rel); + kit_ctx_diagf(ctx, "missing external tree object: %s", rel); return DIST_ERR; } pkg_hash(h, fd.data, fd.size); if (memcmp(h, obj->blake2b, DIST_BLAKE2B_LEN) != 0 || memcmp(h, out->tree, DIST_BLAKE2B_LEN) != 0) { ctx->file_io->release(ctx->file_io->user, &fd); - pkg_diagf(ctx, "tree object hash mismatch"); + kit_ctx_diagf(ctx, "tree object hash mismatch"); return DIST_ERR; } rc = pkg_parse_tree_object(ctx, tree, out->tree, fd.data, fd.size, @@ -1328,7 +1315,7 @@ static int pkg_native_load_tree(const KitContext* ctx, const uint8_t* data, pkg_hash(h, bytes, (size_t)obj->size); if (memcmp(h, obj->blake2b, DIST_BLAKE2B_LEN) != 0 || memcmp(h, out->tree, DIST_BLAKE2B_LEN) != 0) { - pkg_diagf(ctx, "tree object hash mismatch"); + kit_ctx_diagf(ctx, "tree object hash mismatch"); return DIST_ERR; } return pkg_parse_tree_object(ctx, tree, out->tree, bytes, (size_t)obj->size, @@ -1350,7 +1337,7 @@ static int pkg_native_load_index(const KitContext* ctx, const uint8_t* data, } else { char rel[PKG_PATH_BUF]; if (!external_dir) { - pkg_diagf(ctx, "external index is missing"); + kit_ctx_diagf(ctx, "external index is missing"); return DIST_ERR; } if (d->index_url[0]) @@ -1359,7 +1346,7 @@ static int pkg_native_load_index(const KitContext* ctx, const uint8_t* data, DIST_OK) return DIST_ERR; if (pkg_read_external_file(ctx, external_dir, rel, fd) != DIST_OK) { - pkg_diagf(ctx, "missing external index: %s", rel); + kit_ctx_diagf(ctx, "missing external index: %s", rel); return DIST_ERR; } *index_b = fd->data; @@ -1435,7 +1422,7 @@ static int pkg_native_load_stored_chunk( return DIST_ERR; } if (pkg_read_external_file(ctx, external_dir, rel, fd) != DIST_OK) { - pkg_diagf(ctx, "missing external chunk: %s", rel); + kit_ctx_diagf(ctx, "missing external chunk: %s", rel); return DIST_ERR; } *stored = fd->data; @@ -1511,13 +1498,13 @@ static int pkg_verify_native_tree( if (pkg_native_reconstruct_blob(ctx, data, index_b, index_l, d, e, external_dir, chunk_template, &raww) != DIST_OK) { - pkg_diagf(ctx, "native chunk verification failed: %s", e->path); + kit_ctx_diagf(ctx, "native chunk verification failed: %s", e->path); return DIST_ERR; } rawb = kit_writer_mem_bytes(raww, &rawl); if (pkg_verify_blob_bytes(e, rawb, rawl) != DIST_OK) { kit_writer_close(raww); - pkg_diagf(ctx, "blob hash mismatch: %s", e->path); + kit_ctx_diagf(ctx, "blob hash mismatch: %s", e->path); return DIST_ERR; } if (out_dir && @@ -1550,7 +1537,7 @@ static int pkg_verify_native(const KitContext* ctx, const KitCasHost* host, int rc = DIST_ERR; if (dist_kpkg3_read_header(data, len, &h) != DIST_OK || pkg_bounds3(&h, len) != DIST_OK) { - pkg_diagf(ctx, "malformed native package"); + kit_ctx_diagf(ctx, "malformed native package"); return DIST_ERR; } if (pkg_verify_manifest(ctx, data + h.manifest_offset, @@ -1562,7 +1549,7 @@ static int pkg_verify_native(const KitContext* ctx, const KitCasHost* host, (size_t)h.descriptor_signature_size, desc_keyid) != DIST_OK || memcmp(desc_keyid, v->keyid, DIST_KEYID_LEN) != 0) { - pkg_diagf(ctx, "encoding descriptor signer mismatch"); + kit_ctx_diagf(ctx, "encoding descriptor signer mismatch"); return DIST_ERR; } if (dist_minisig_verify(data + h.descriptor_signature_offset, @@ -1570,13 +1557,13 @@ static int pkg_verify_native(const KitContext* ctx, const KitCasHost* host, data + h.descriptor_offset, (size_t)h.descriptor_size, v->pk, desc_trusted, sizeof desc_trusted) != DIST_OK) { - pkg_diagf(ctx, "encoding descriptor signature FAILED"); + kit_ctx_diagf(ctx, "encoding descriptor signature FAILED"); return DIST_ERR; } if (dist_kpkg3_descriptor_parse(data + h.descriptor_offset, (size_t)h.descriptor_size, &d, err, sizeof err) != DIST_OK) { - pkg_diagf(ctx, "encoding descriptor: %s", err); + kit_ctx_diagf(ctx, "encoding descriptor: %s", err); return DIST_ERR; } if (memcmp(d.package_id, v->package_id, DIST_BLAKE2B_LEN) != 0 || @@ -1585,7 +1572,7 @@ static int pkg_verify_native(const KitContext* ctx, const KitCasHost* host, !pkg_range_ok(d.tree_offset, d.tree_size, len) || !pkg_range_ok(d.index_offset, d.index_size, len) || !pkg_range_ok(d.content_offset, d.content_size, len)) { - pkg_diagf(ctx, "encoding descriptor does not match package layout"); + kit_ctx_diagf(ctx, "encoding descriptor does not match package layout"); return DIST_ERR; } dist_kpkg3_region_root(tree_root, "tree", data + d.tree_offset, @@ -1594,7 +1581,7 @@ static int pkg_verify_native(const KitContext* ctx, const KitCasHost* host, (size_t)d.content_size); if (pkg_native_load_index(ctx, data, &d, external_dir, &index_fd, &index_b, &index_l) != DIST_OK) { - pkg_diagf(ctx, "native package index verification failed"); + kit_ctx_diagf(ctx, "native package index verification failed"); goto done; } dist_kpkg3_region_root(index_root, "index", index_b, index_l); @@ -1602,17 +1589,17 @@ static int pkg_verify_native(const KitContext* ctx, const KitCasHost* host, chunk_template = pkg_descriptor_chunk_template(&d); if (d.index_bytes && !pkg_descriptor_has_embedded_chunks(&d) && !external_dir) { - pkg_diagf(ctx, "external native chunks are missing"); + kit_ctx_diagf(ctx, "external native chunks are missing"); goto done; } if (memcmp(tree_root, d.tree_root, DIST_BLAKE2B_LEN) != 0 || memcmp(index_root, d.index_root, DIST_BLAKE2B_LEN) != 0 || memcmp(content_root, d.content_root, DIST_BLAKE2B_LEN) != 0) { - pkg_diagf(ctx, "native package region hash mismatch"); + kit_ctx_diagf(ctx, "native package region hash mismatch"); goto done; } if (pkg_verify_native_index_sorted(index_b, index_l, &d) != DIST_OK) { - pkg_diagf(ctx, "native chunk index is malformed"); + kit_ctx_diagf(ctx, "native chunk index is malformed"); goto done; } def = pkg_default_output(&v->manifest); @@ -1682,7 +1669,7 @@ KitStatus kit_pkg_inspect(const KitContext* ctx, const uint8_t* pkg_data, size_t region_len; if (dist_kpkg3_read_header(pkg_data, pkg_len, &h) != DIST_OK || pkg_bounds3(&h, pkg_len) != DIST_OK) { - pkg_diagf(ctx, "malformed native package"); + kit_ctx_diagf(ctx, "malformed native package"); return KIT_MALFORMED; } if (show_encoding) { @@ -1706,7 +1693,7 @@ KitStatus kit_pkg_keygen(const KitContext* ctx, KitPkgRandomFn rng, if (!rng || !pub_out || !sec_out) return KIT_INVALID; if (rng(rng_user, seed, sizeof seed) != 0 || rng(rng_user, keyid, sizeof keyid) != 0) { - pkg_diagf(ctx, "keygen: failed to read system randomness"); + kit_ctx_diagf(ctx, "keygen: failed to read system randomness"); return KIT_ERR; } dist_minisig_keygen(&kp, seed, keyid); diff --git a/src/arch/aa64/asm.c b/src/arch/aa64/asm.c @@ -540,42 +540,7 @@ static void emit32(AsmDriver* d, u32 word) { static int parse_cond_from_ident(AsmDriver* d, Sym ident, u32* out) { Slice sl = pool_slice(asm_driver_pool(d), ident); - const char* s = sl.s; - size_t n = sl.len; - if (!s) return 0; - if (icase_eq(s, n, "eq")) - *out = 0; - else if (icase_eq(s, n, "ne")) - *out = 1; - else if (icase_eq(s, n, "cs") || icase_eq(s, n, "hs")) - *out = 2; - else if (icase_eq(s, n, "cc") || icase_eq(s, n, "lo")) - *out = 3; - else if (icase_eq(s, n, "mi")) - *out = 4; - else if (icase_eq(s, n, "pl")) - *out = 5; - else if (icase_eq(s, n, "vs")) - *out = 6; - else if (icase_eq(s, n, "vc")) - *out = 7; - else if (icase_eq(s, n, "hi")) - *out = 8; - else if (icase_eq(s, n, "ls")) - *out = 9; - else if (icase_eq(s, n, "ge")) - *out = 10; - else if (icase_eq(s, n, "lt")) - *out = 11; - else if (icase_eq(s, n, "gt")) - *out = 12; - else if (icase_eq(s, n, "le")) - *out = 13; - else if (icase_eq(s, n, "al")) - *out = 14; - else - return 0; - return 1; + return aa64_cond_from_name(sl.s, sl.len, out); } static u32 parse_cond(AsmDriver* d, const char* what) { diff --git a/src/arch/aa64/disasm.c b/src/arch/aa64/disasm.c @@ -35,11 +35,6 @@ typedef struct AA64Disasm { StrBuf ann; } AA64Disasm; -static const char* aa64_cond_names[16] = { - "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc", - "hi", "ls", "ge", "lt", "gt", "le", "al", "nv", -}; - static void aa64_write_mnemonic(AA64Disasm* d, const AA64InsnDesc* desc, u32 word) { strbuf_reset(&d->mnem); @@ -48,7 +43,7 @@ static void aa64_write_mnemonic(AA64Disasm* d, const AA64InsnDesc* desc, * target. Matches GNU as / objdump conventions. */ u32 cond = word & 0xfu; strbuf_puts(&d->mnem, "b."); - strbuf_puts(&d->mnem, aa64_cond_names[cond]); + strbuf_puts(&d->mnem, aa64_cond_name(cond)); return; } if (desc->fmt == AA64_FMT_BITFIELD) { diff --git a/src/arch/aa64/isa.c b/src/arch/aa64/isa.c @@ -116,6 +116,39 @@ const char* aa64_sysreg_name(u32 op0, u32 op1, u32 crn, u32 crm, u32 op2) { return NULL; } +/* Canonical 4-bit condition-code index -> mnemonic suffix. Single source + * of truth for the disassembler (b.<cond>, CSEL aliases) and the + * codegen/assembler emit path. Order is the architectural cond encoding: + * EQ=0, NE=1, CS=2, CC=3, ... AL=14, NV=15. */ +static const char* aa64_cond_names[16] = { + "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc", + "hi", "ls", "ge", "lt", "gt", "le", "al", "nv", +}; + +const char* aa64_cond_name(u32 cond) { return aa64_cond_names[cond & 0xfu]; } + +int aa64_cond_from_name(const char* s, size_t n, u32* out) { + u32 c; + if (!s) return 0; + /* Aliases: HS (higher-or-same, unsigned) is CS=2; LO (lower, unsigned) + * is CC=3. NV=15 is intentionally not accepted as an input spelling. */ + if (aa64_icase_eq(s, n, "hs")) { + *out = 2u; + return 1; + } + if (aa64_icase_eq(s, n, "lo")) { + *out = 3u; + return 1; + } + for (c = 0; c < 15u; ++c) { + if (aa64_icase_eq(s, n, aa64_cond_names[c])) { + *out = c; + return 1; + } + } + return 0; +} + const AA64InsnDesc aa64_insn_table[] = { /* ----- Move-wide immediate (MOVN / MOVZ / MOVK) ----- */ {MN("movn"), 0x12800000u, 0x7F800000u, AA64_FMT_MOVEWIDE, 0, {0, 0}}, @@ -646,10 +679,7 @@ static void emit_vreg(StrBuf* sb, u32 r, char prefix) { } static void emit_cond(StrBuf* sb, u32 cond) { - static const char* names[16] = {"eq", "ne", "cs", "cc", "mi", "pl", - "vs", "vc", "hi", "ls", "ge", "lt", - "gt", "le", "al", "nv"}; - strbuf_puts(sb, names[cond & 0xfu]); + strbuf_puts(sb, aa64_cond_name(cond)); } /* Sign-extend an n-bit value held in the low bits of v to i64. */ diff --git a/src/arch/aa64/isa.h b/src/arch/aa64/isa.h @@ -1121,6 +1121,15 @@ int aa64_sysreg_by_name(const char* s, size_t n, u32* op0, u32* op1, u32* crn, * spelling instead). */ const char* aa64_sysreg_name(u32 op0, u32 op1, u32 crn, u32 crm, u32 op2); +/* Condition-code <-> name, the single source of truth shared by the + * disassembler, the codegen/assembler print path, and the assembler parse. + * aa64_cond_name returns the canonical lowercase suffix for a 4-bit cond + * (masked to bits[3:0]); EQ=0..AL=14, NV=15. aa64_cond_from_name parses a + * name (case-insensitive, length n) back to its index, accepting the HS/LO + * aliases for CS/CC; it rejects NV. Returns 1 on a hit, 0 otherwise. */ +const char* aa64_cond_name(u32 cond); +int aa64_cond_from_name(const char* s, size_t n, u32* out); + /* ==================================================================== * Load/store pair, signed-offset (STP / LDP, no pre/post-increment). * opc(2) 101 V(1) 010 L(1) imm7 Rt2 Rn Rt (bit 23 = 0) diff --git a/src/arch/c_target/c_emit.c b/src/arch/c_target/c_emit.c @@ -24,6 +24,7 @@ #include "core/heap.h" #include "core/pool.h" #include "core/slice.h" +#include "core/vec.h" #include "obj/format.h" #include "obj/obj.h" @@ -45,6 +46,41 @@ CGLocal c_emit_local(CTarget* t, const CGLocalDesc* d); * Same forward-decl trick as obj_tls.c uses. */ ObjSymId obj_tlv_bootstrap_get(const ObjBuilder*); +/* === Growable-array helpers === + * + * The C target's growable tables are heap-backed (allocated from + * t->c->ctx->heap and freed in c_emit_destroy), so they route through + * core/vec.h's VEC_GROW rather than hand-rolling the doubling realloc. + * + * c_vec_grow_or_panic: ensure capacity >= want, panicking on OOM. Used by + * append-style tables (scopes, local_static_*) where the live region is + * tracked by a separate count and the grown tail is never read before being + * written. + * + * c_vec_grow_zeroed: same, but zero-fills the newly grown tail. Used by + * index-addressed tables (type_state, local_*, sym_forwarded) that are read + * at arbitrary indices and rely on a zero default. VEC_GROW does not zero, + * so we capture the pre-grow cap and clear [old_cap, new_cap). + * + * Both are macros so VEC_GROW can derive element size/alignment from *ptr. */ +#define c_vec_grow_or_panic(t, ptr, cap, want) \ + do { \ + if (VEC_GROW((t)->c->ctx->heap, (ptr), (cap), (want))) { \ + compiler_panic((t)->c, (SrcLoc){0, 0, 0}, \ + "C target: out of memory"); \ + } \ + } while (0) + +#define c_vec_grow_zeroed(t, ptr, cap, want) \ + do { \ + u32 c_vgz_old_ = (cap); \ + c_vec_grow_or_panic((t), (ptr), (cap), (want)); \ + if ((cap) > c_vgz_old_) { \ + memset((ptr) + c_vgz_old_, 0, \ + ((size_t)((cap) - c_vgz_old_)) * sizeof(*(ptr))); \ + } \ + } while (0) + /* === Target state === */ void c_emit_target_init(CTarget* t, Compiler* c, ObjBuilder* o, KitWriter* w) { @@ -131,16 +167,7 @@ static u32 c_int_width_for_signedness(CTarget* t, KitCgTypeId type) { * used for indirect calls and function-pointer-typed values. */ static void c_grow_type_state(CTarget* t, u32 needed) { - Heap* h = t->c->ctx->heap; - u32 newcap = t->type_state_cap ? t->type_state_cap : 32; - while (newcap < needed) newcap *= 2; - u8* nd = (u8*)h->realloc(h, t->type_state, t->type_state_cap, newcap, 1); - if (!nd && newcap) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); - } - for (u32 i = t->type_state_cap; i < newcap; ++i) nd[i] = 0; - t->type_state = nd; - t->type_state_cap = newcap; + c_vec_grow_zeroed(t, t->type_state, t->type_state_cap, needed); } static const char* c_typedef_name(CTarget* t, KitCgTypeId tid) { @@ -372,23 +399,15 @@ void c_local_name(CLocal r, char* out, size_t cap) { } static void c_grow_local_table(CTarget* t, u32 needed) { - Heap* h = t->c->ctx->heap; - u32 newcap = t->local_cap ? t->local_cap : 16; - while (newcap < needed) newcap *= 2; - u8* nd = (u8*)h->realloc(h, t->local_declared, t->local_cap, newcap, 1); - KitCgTypeId* nt = (KitCgTypeId*)h->realloc( - h, t->local_type, t->local_cap * sizeof(KitCgTypeId), - newcap * sizeof(KitCgTypeId), _Alignof(KitCgTypeId)); - if ((!nd && newcap) || (!nt && newcap)) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); - } - for (u32 i = t->local_cap; i < newcap; ++i) { - nd[i] = 0; - nt[i] = 0; - } - t->local_declared = nd; - t->local_type = nt; - t->local_cap = newcap; + /* Two parallel arrays kept at a single shared local_cap. Grow each from the + * same old cap to the same new cap via independent cap trackers, then commit + * the shared cap once. (They always grow together, so both reach the same + * VEC_GROW-derived capacity.) */ + u32 declared_cap = t->local_cap; + u32 type_cap = t->local_cap; + c_vec_grow_zeroed(t, t->local_declared, declared_cap, needed); + c_vec_grow_zeroed(t, t->local_type, type_cap, needed); + t->local_cap = declared_cap; } /* Emit the trailing `__attribute__((unused)) = INIT;` for a local decl of @@ -1001,22 +1020,19 @@ void c_emit_func_begin(CTarget* t, const CGFuncDesc* fd) { t->fn_body_start = t->body.len; } -void c_ensure_forward_decl(CTarget* t, ObjSymId sym, KitCgTypeId fn_type) { - Heap* h = t->c->ctx->heap; - if ((u32)sym >= t->sym_forwarded_cap) { - u32 newcap = t->sym_forwarded_cap ? t->sym_forwarded_cap : 16; - while (newcap <= (u32)sym) newcap *= 2; - u8* nd = - (u8*)h->realloc(h, t->sym_forwarded, t->sym_forwarded_cap, newcap, 1); - if (!nd && newcap) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); - } - for (u32 i = t->sym_forwarded_cap; i < newcap; ++i) nd[i] = 0; - t->sym_forwarded = nd; - t->sym_forwarded_cap = newcap; - } - if (t->sym_forwarded[sym]) return; +/* Test-and-set on the sym_forwarded bitmap, growing it as needed. Returns 1 if + * `sym` was already marked (caller should skip re-emitting its forward decl), + * 0 after marking it for the first time. Shared by c_ensure_forward_decl and + * c_emit_alias, which both emit a decl that doubles as a forward prototype. */ +static int c_sym_forwarded_test_and_set(CTarget* t, ObjSymId sym) { + c_vec_grow_zeroed(t, t->sym_forwarded, t->sym_forwarded_cap, (u32)sym + 1u); + if (t->sym_forwarded[sym]) return 1; t->sym_forwarded[sym] = 1; + return 0; +} + +void c_ensure_forward_decl(CTarget* t, ObjSymId sym, KitCgTypeId fn_type) { + if (c_sym_forwarded_test_and_set(t, sym)) return; const char* name = c_sym_name(t, sym); const ObjSym* os = obj_symbol_get(t->obj, sym); if ((os && (os->kind == SK_FUNC || os->kind == SK_IFUNC)) || fn_type != 0) { @@ -1539,17 +1555,7 @@ void c_emit_cmp_branch(CTarget* t, CmpOp op, Operand a, Operand b, Label l) { * cosmetic; gcc/clang fold it. */ static void c_grow_scopes(CTarget* t, u32 needed) { - Heap* h = t->c->ctx->heap; - u32 newcap = t->scopes_cap ? t->scopes_cap : 8; - while (newcap < needed) newcap *= 2; - CScopeInfo* ns = (CScopeInfo*)h->realloc( - h, t->scopes, t->scopes_cap * sizeof(CScopeInfo), - newcap * sizeof(CScopeInfo), _Alignof(CScopeInfo)); - if (!ns && newcap) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); - } - t->scopes = ns; - t->scopes_cap = newcap; + c_vec_grow_or_panic(t, t->scopes, t->scopes_cap, needed); } CGScope c_emit_scope_begin(CTarget* t, const CGScopeDesc* d) { @@ -1702,40 +1708,26 @@ static int c_is_local_static_sym(CTarget* t, ObjSymId sym) { } static void c_mark_local_static_sym(CTarget* t, ObjSymId sym) { - Heap* h = t->c->ctx->heap; if (sym == OBJ_SYM_NONE || c_is_local_static_sym(t, sym)) return; - if (t->local_static_nsyms + 1u > t->local_static_syms_cap) { - u32 oldcap = t->local_static_syms_cap; - u32 newcap = oldcap ? oldcap * 2u : 16u; - ObjSymId* ns = (ObjSymId*)h->realloc( - h, t->local_static_syms, oldcap * sizeof(*t->local_static_syms), - newcap * sizeof(*t->local_static_syms), _Alignof(ObjSymId)); - if (!ns) { - compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, - "C target: out of memory"); - } - t->local_static_syms = ns; - t->local_static_syms_cap = newcap; + /* Append-style: VEC_GROW for the doubling realloc, but keep the + * cur_fn-relative panic location this path uses on OOM. */ + if (VEC_GROW(t->c->ctx->heap, t->local_static_syms, t->local_static_syms_cap, + t->local_static_nsyms + 1u)) { + compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, + "C target: out of memory"); } t->local_static_syms[t->local_static_nsyms++] = sym; } static void c_grow_local_static_entries(CTarget* t, u32 want) { - Heap* h = t->c->ctx->heap; - if (want <= t->local_static_entries_cap) return; - u32 oldcap = t->local_static_entries_cap; - u32 newcap = oldcap ? oldcap * 2u : 8u; - while (newcap < want) newcap *= 2u; - CLocalStaticLabelEntry* ne = (CLocalStaticLabelEntry*)h->realloc( - h, t->local_static_entries, oldcap * sizeof(*t->local_static_entries), - newcap * sizeof(*t->local_static_entries), - _Alignof(CLocalStaticLabelEntry)); - if (!ne) { + /* Append-style (the caller initializes the [0, count) entries after this + * grows): VEC_GROW for the doubling realloc, with the cur_fn-relative panic + * location this path uses on OOM. */ + if (VEC_GROW(t->c->ctx->heap, t->local_static_entries, + t->local_static_entries_cap, want)) { compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, "C target: out of memory"); } - t->local_static_entries = ne; - t->local_static_entries_cap = newcap; } int c_emit_can_local_static_data(CTarget* t, @@ -2360,21 +2352,7 @@ void c_emit_unreachable(CTarget* t) { * for callers, so we mark sym_forwarded to dedup against a later c_call. */ void c_emit_alias(CTarget* t, ObjSymId alias_sym, ObjSymId target_sym, KitCgTypeId type) { - Heap* h = t->c->ctx->heap; - if ((u32)alias_sym >= t->sym_forwarded_cap) { - u32 newcap = t->sym_forwarded_cap ? t->sym_forwarded_cap : 16; - while (newcap <= (u32)alias_sym) newcap *= 2; - u8* nd = - (u8*)h->realloc(h, t->sym_forwarded, t->sym_forwarded_cap, newcap, 1); - if (!nd && newcap) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); - } - for (u32 i = t->sym_forwarded_cap; i < newcap; ++i) nd[i] = 0; - t->sym_forwarded = nd; - t->sym_forwarded_cap = newcap; - } - if (t->sym_forwarded[alias_sym]) return; - t->sym_forwarded[alias_sym] = 1; + if (c_sym_forwarded_test_and_set(t, alias_sym)) return; const char* alias_name = c_sym_name(t, alias_sym); const char* target_name = c_sym_name(t, target_sym); const CgType* fty = cg_type_get(t->c, api_unalias_type(t->c, type)); diff --git a/src/arch/riscv/reloc.c b/src/arch/riscv/reloc.c @@ -12,10 +12,11 @@ * AUIPC+JALR pair; R_PLT32 keeps its neutral 4-byte width (a gate value — * the apply path re-derives the real span from the kind). * - * RELAX / TPREL_ADD are relaxation markers (no bytes); SET/SUB_ULEB128 are - * variable-width (the apply path reads the true field length from the bytes - * — the width here is the nominal gate value). R_RV_ALIGN is skipped - * before the reloc record is built and carries no descriptor. */ + * RELAX / TPREL_ADD / ALIGN are relaxation markers (RELOC_MARKER): no reloc + * record is emitted for them — the link layout pass drops them via + * reloc_kind_is_marker, so the width here is an unused gate value. + * SET/SUB_ULEB128 are variable-width (RELOC_WIDTH_DYN): the apply path reads + * the true field length from the bytes, so the width here is only nominal. */ #include "obj/reloc.h" @@ -42,6 +43,7 @@ static const RelocDescRow rv_rows[] = { {R_RV_RVC_JUMP, {2, 0}}, {R_RV_RELAX, {4, RELOC_MARKER}}, {R_RV_TPREL_ADD, {4, RELOC_MARKER}}, + {R_RV_ALIGN, {4, RELOC_MARKER}}, {R_ADD8, {1, 0}}, {R_SUB8, {1, 0}}, {R_SUB6, {1, 0}}, diff --git a/src/core/diag.c b/src/core/diag.c @@ -23,3 +23,11 @@ void diag_emitv(DiagSink* s, DiagKind k, SrcLoc loc, const char* fmt, } if (s && s->emit) s->emit(s, k, loc, fmt, ap); } + +void kit_ctx_diagf(const KitContext* ctx, const char* fmt, ...) { + va_list ap; + if (!ctx || !ctx->diag) return; + va_start(ap, fmt); + diag_emitv(ctx->diag, DIAG_ERROR, SRCLOC_NONE, fmt, ap); + va_end(ap); +} diff --git a/src/core/diag.h b/src/core/diag.h @@ -16,4 +16,9 @@ typedef KitDiagKind DiagKind; void diag_emit(DiagSink*, DiagKind, SrcLoc, const char* fmt, ...); void diag_emitv(DiagSink*, DiagKind, SrcLoc, const char* fmt, va_list); +/* Emit a DIAG_ERROR with no source location through the context's diag sink. + * No-op when `ctx` or `ctx->diag` is NULL. Shared by the public-API bridges + * (core/cas/compress/package) for human-readable operational errors. */ +void kit_ctx_diagf(const KitContext* ctx, const char* fmt, ...); + #endif diff --git a/src/link/link_jit.c b/src/link/link_jit.c @@ -790,23 +790,19 @@ static void jit_append_obj_inner(KitJit* jit, ObjBuilder* ob) { if (jit->linker->resolver(jit->linker->resolver_user, nm_s)) ok = 1; } if (!ok && s->bind == SB_WEAK) ok = 1; + /* Format pseudo-symbols the JIT image satisfies internally (Mach-O + * __tlv_bootstrap, COFF _tls_index): the obj layer is the single + * arbiter so src/link names no pseudo-symbol. */ + if (!ok && obj_format_jit_undef_internal(jit->c, s->name)) ok = 1; if (!ok) { Slice nm_s = pool_slice(jit->c->global, s->name); const char* nm = nm_s.s; size_t nlen = nm_s.len; - if (nm && nlen == 15u && memcmp(nm, "__tlv_bootstrap", 15u) == 0) - ok = 1; - /* Windows COFF TLS module-index symbol: the JIT relaxes every TLS - * access to in-image addressing, so `_tls_index` is never read. */ - if (nm && nlen == 10u && memcmp(nm, "_tls_index", 10u) == 0) - ok = 1; - if (!ok) { - obj_format_demangle_c(jit->c, &nm, &nlen); - obj_symiter_free(it); - compiler_panic(jit->c, SRCLOC_NONE, - "kit_jit_append_obj: undefined reference to '%.*s'", - (int)nlen, nm); - } + obj_format_demangle_c(jit->c, &nm, &nlen); + obj_symiter_free(it); + compiler_panic(jit->c, SRCLOC_NONE, + "kit_jit_append_obj: undefined reference to '%.*s'", + (int)nlen, nm); } } } @@ -988,16 +984,21 @@ static void jit_append_obj_inner(KitJit* jit, ObjBuilder* ob) { s->defined = 1; continue; } + /* Format pseudo-symbols the JIT image satisfies internally (Mach-O + * __tlv_bootstrap, COFF _tls_index): define as 0 so resolution does not + * reject them. Same obj-layer arbiter as the preflight pass above, so the + * two no longer disagree (a non-weak undef _tls_index in an appended COFF + * object used to pass preflight then panic here). */ + if (obj_format_jit_undef_internal(jit->c, s->name)) { + s->kind = SK_ABS; + s->vaddr = 0; + s->defined = 1; + continue; + } if (s->name != 0) { Slice nm_s = pool_slice(jit->c->global, s->name); const char* nm = nm_s.s; size_t nlen = nm_s.len; - if (nm && nlen == 15u && memcmp(nm, "__tlv_bootstrap", 15u) == 0) { - s->kind = SK_ABS; - s->vaddr = 0; - s->defined = 1; - continue; - } obj_format_demangle_c(jit->c, &nm, &nlen); compiler_panic(jit->c, SRCLOC_NONE, "kit_jit_append_obj: undefined reference to '%.*s'", diff --git a/src/link/link_reloc_desc.h b/src/link/link_reloc_desc.h @@ -87,4 +87,19 @@ static inline int reloc_kind_is_secrel(const Compiler* c, RelocKind k) { return d && (d->flags & RELOC_IS_SECREL) ? 1 : 0; } +/* Relaxation marker that patches no bytes (RISC-V RELAX / TPREL_ADD / ALIGN). + * No reloc record is emitted for these — they are dropped during layout. */ +static inline int reloc_kind_is_marker(const Compiler* c, RelocKind k) { + const RelocDesc* d = reloc_desc(c, k); + return d && (d->flags & RELOC_MARKER) ? 1 : 0; +} + +/* Variable-width kind (RISC-V ULEB128 SET/SUB): the descriptor width is only a + * nominal gate value; the true field length is read from the bytes at apply + * time, so the layout pass must bound-check the offset before the apply scan. */ +static inline int reloc_kind_is_width_dyn(const Compiler* c, RelocKind k) { + const RelocDesc* d = reloc_desc(c, k); + return d && (d->flags & RELOC_WIDTH_DYN) ? 1 : 0; +} + #endif diff --git a/src/link/link_reloc_layout.c b/src/link/link_reloc_layout.c @@ -934,9 +934,7 @@ void link_emit_relocations(Linker* l, LinkImage* img, const LinkSymId* got_map, if (!s || (!link_section_kept(s) && !link_section_kept_fileonly(s))) continue; if (link_input_reloc_section(m, r, k) == LINK_SEC_NONE) continue; - if (r->kind == R_RV_RELAX || r->kind == R_RV_TPREL_ADD || - r->kind == R_RV_ALIGN) - continue; + if (reloc_kind_is_marker(l->c, (RelocKind)r->kind)) continue; if (r->sym == OBJ_SYM_NONE || r->sym >= m->nsym) compiler_panic(l->c, SRCLOC_NONE, "link: reloc references unknown symbol"); @@ -979,8 +977,7 @@ void link_emit_relocations(Linker* l, LinkImage* img, const LinkSymId* got_map, * Guard the offset against the section end here — where the size is * known — so the apply-time scan starts in-bounds even for a malformed * external object. */ - if ((rec.kind == R_SET_ULEB128 || rec.kind == R_SUB_ULEB128) && - rec.offset >= ls->size) + if (reloc_kind_is_width_dyn(l->c, rec.kind) && rec.offset >= ls->size) compiler_panic(l->c, SRCLOC_NONE, "link: ULEB128 reloc offset past section end"); *link_append_reloc_slot(img) = rec; diff --git a/src/link/link_resolve.c b/src/link/link_resolve.c @@ -510,34 +510,21 @@ void link_resolve_undefs(Linker* l, LinkImage* img) { s->defined = 1; continue; } - /* JIT lane: Mach-O inputs (including clang-produced .o files) - * carry a non-weak undef `__tlv_bootstrap` on every TLV var. - * kit_jit_from_image rewrites every descriptor's slot[0] to our - * thunk, so the resolved value never gets read — but we still need - * resolve_undefs to not panic. Treat the symbol as weak-undef - * (vaddr = 0, SK_ABS) in JIT mode only; AOT lanes keep the strict - * "undefined external" semantics. */ - if (l->jit_mode && s->name != 0) { - Slice nm_s = pool_slice(l->c->global, s->name); - const char* nm = nm_s.s; - size_t nlen = nm_s.len; - if (nm && nlen == 15u && memcmp(nm, "__tlv_bootstrap", 15u) == 0) { - s->kind = SK_ABS; - s->vaddr = 0; - s->defined = 1; - continue; - } - /* Windows COFF Local-Exec TLS: the in-process JIT relaxes every TLS - * access to in-image addressing (see kit_jit_from_image), so the PE - * module-index symbol `_tls_index` — normally supplied by the OS loader - * via the TLS directory — is never read. Define it as 0 so resolve does - * not reject it; its idiom relocs are dropped in the JIT reloc pass. */ - if (nm && nlen == 10u && memcmp(nm, "_tls_index", 10u) == 0) { - s->kind = SK_ABS; - s->vaddr = 0; - s->defined = 1; - continue; - } + /* JIT lane: format pseudo-symbols the JIT image satisfies internally. + * Mach-O inputs (including clang-produced .o files) carry a non-weak undef + * `__tlv_bootstrap` on every TLV var; kit_jit_from_image rewrites every + * descriptor's slot[0] to our thunk, so the resolved value never gets read. + * Windows COFF inputs carry `_tls_index` (normally supplied by the OS + * loader via the TLS directory); the JIT relaxes every TLS access to + * in-image addressing, so it is never read. Treat such a symbol as + * weak-undef (vaddr = 0, SK_ABS) in JIT mode only; AOT lanes keep the + * strict "undefined external" semantics. The obj layer is the single + * arbiter (shared with link_jit.c), so src/link names no pseudo-symbol. */ + if (l->jit_mode && obj_format_jit_undef_internal(l->c, s->name)) { + s->kind = SK_ABS; + s->vaddr = 0; + s->defined = 1; + continue; } { Slice nm_s = s->name ? pool_slice(l->c->global, s->name) : SLICE_NULL; diff --git a/src/obj/obj.h b/src/obj/obj.h @@ -817,6 +817,15 @@ ObjTlsModel obj_format_tls_model(const Compiler*); * obj_format_tls_model as the TLS-mechanism authority. */ int obj_format_jit_drops_symbol_ref(const Compiler*, Sym name); +/* In-process JIT: true when an *undefined* reference to `name` is satisfied + * internally by the JIT image and so needs no external definition: Mach-O + * `__tlv_bootstrap` (descriptor slot[0] rewritten to the JIT thunk) and COFF + * `_tls_index` (TLS access relaxed to in-image addressing). The single arbiter + * for every JIT undef-accept site, keeping src/link free of pseudo-symbol + * names. Broader than obj_format_jit_drops_symbol_ref, which gates reloc + * dropping and covers only `_tls_index`. */ +int obj_format_jit_undef_internal(const Compiler*, Sym name); + /* True when the active object format carries DWARF debug sections * file-only (not mapped into a loadable segment): ELF / Mach-O yes, * COFF no. */ diff --git a/src/obj/obj_tls.c b/src/obj/obj_tls.c @@ -75,9 +75,41 @@ int obj_format_jit_drops_symbol_ref(const Compiler* c, Sym name) { return nm.len == 10u && memcmp(nm.s, "_tls_index", 10u) == 0; } -static void define_tls_elf(ObjBuilder* ob, Compiler* c, ObjSymId sym, - const u8* data, u32 size, int has_nonzero_init, - u32 align, const ObjTlsReloc* relocs, u32 nrelocs) { +/* In-process JIT: 1 when an *undefined* reference to `name` needs no external + * definition because the JIT image satisfies it internally. Two format + * pseudo-symbols qualify: + * - Mach-O `__tlv_bootstrap`: every TLV descriptor's slot[0] is rewritten to + * the JIT's own thunk, so the bootstrap value is never read. + * - COFF `_tls_index`: the JIT relaxes every TLS access to in-image + * addressing (its idiom relocs are dropped — see + * obj_format_jit_drops_symbol_ref), so the module index is never read. + * The single arbiter funneling every JIT undef-accept site, so src/link names + * no pseudo-symbol. Distinct from obj_format_jit_drops_symbol_ref, which gates + * *reloc dropping* and is COFF-only: `__tlv_bootstrap` relocs are rewritten, + * not dropped, so it must not appear there. */ +int obj_format_jit_undef_internal(const Compiler* c, Sym name) { + Slice nm; + if (!c || name == 0) return 0; + nm = pool_slice(c->global, name); + if (nm.len == 15u && memcmp(nm.s, "__tlv_bootstrap", 15u) == 0) return 1; + if (nm.len == 10u && memcmp(nm.s, "_tls_index", 10u) == 0) return 1; + return 0; +} + +/* Emit the per-thread storage for a `_Thread_local` and bind `store_sym` + * onto it. Initialized data lands in `.tdata` / `__DATA,__thread_data` + * (with the pointer-init relocs replayed); zero-init lands in `.tbss` / + * `__DATA,__thread_bss`. Identical across ELF and Mach-O — the only format + * difference is *which* symbol receives the storage definition (ELF binds the + * user-visible symbol directly; Mach-O binds the private `<name>$tlv$init` + * data symbol, leaving the user symbol for the descriptor). Section spelling + * is already format-aware via obj_secname_t{data,bss}; macho_emit's + * section_flags_for maps SF_TLS + sectname to the right S_THREAD_LOCAL_* type + * on that side. */ +static void tls_emit_storage(ObjBuilder* ob, Compiler* c, ObjSymId store_sym, + const u8* data, u32 size, int has_nonzero_init, + u32 align, const ObjTlsReloc* relocs, + u32 nrelocs) { u32 a = align ? align : 1u; if (!data || !has_nonzero_init) { Sym sname = obj_secname_tbss(c); @@ -86,7 +118,7 @@ static void define_tls_elf(ObjBuilder* ob, Compiler* c, ObjSymId sym, SF_ALLOC | SF_WRITE | SF_TLS, a, 0, OBJ_SEC_NONE, 0); u32 base = obj_align_to(ob, sec, a); obj_reserve_bss(ob, sec, base + size, a); - obj_symbol_define(ob, sym, sec, base, size); + obj_symbol_define(ob, store_sym, sec, base, size); return; } Sym sname = obj_secname_tdata(c); @@ -97,13 +129,20 @@ static void define_tls_elf(ObjBuilder* ob, Compiler* c, ObjSymId sym, u8* dst = obj_reserve(ob, sec, size); if (dst) memcpy(dst, data, size); } - obj_symbol_define(ob, sym, sec, base, size); + obj_symbol_define(ob, store_sym, sec, base, size); for (u32 i = 0; i < nrelocs; ++i) { obj_reloc(ob, sec, base + relocs[i].offset, relocs[i].kind, relocs[i].target, relocs[i].addend); } } +static void define_tls_elf(ObjBuilder* ob, Compiler* c, ObjSymId sym, + const u8* data, u32 size, int has_nonzero_init, + u32 align, const ObjTlsReloc* relocs, u32 nrelocs) { + tls_emit_storage(ob, c, sym, data, size, has_nonzero_init, align, relocs, + nrelocs); +} + static ObjSymId tlv_bootstrap(ObjBuilder* ob, Compiler* c) { ObjSymId s = obj_tlv_bootstrap_get(ob); if (s != OBJ_SYM_NONE) return s; @@ -145,33 +184,10 @@ static void define_tls_macho(ObjBuilder* ob, Compiler* c, ObjSymId sym, "obj_define_tls: descriptor sym not found"); ObjSymId data_sym = mint_init_sym(ob, c, desc_os->name); - /* Storage section: __thread_data (initialized) or __thread_bss (BSS). - * Same SF_TLS flag as ELF — macho_emit's section_flags_for maps SF_TLS - * + sectname to the right S_THREAD_LOCAL_* type. */ - u32 a = align ? align : 1u; - if (!data || !has_nonzero_init) { - Sym sname = obj_secname_tbss(c); - ObjSecId sec = - obj_section_ex(ob, sname, SEC_BSS, SSEM_NOBITS, - SF_ALLOC | SF_WRITE | SF_TLS, a, 0, OBJ_SEC_NONE, 0); - u32 base = obj_align_to(ob, sec, a); - obj_reserve_bss(ob, sec, base + size, a); - obj_symbol_define(ob, data_sym, sec, base, size); - } else { - Sym sname = obj_secname_tdata(c); - ObjSecId sec = - obj_section(ob, sname, SEC_DATA, SF_ALLOC | SF_WRITE | SF_TLS, a); - u32 base = obj_align_to(ob, sec, a); - { - u8* dst = obj_reserve(ob, sec, size); - if (dst) memcpy(dst, data, size); - } - obj_symbol_define(ob, data_sym, sec, base, size); - for (u32 i = 0; i < nrelocs; ++i) { - obj_reloc(ob, sec, base + relocs[i].offset, relocs[i].kind, - relocs[i].target, relocs[i].addend); - } - } + /* Storage under the private `<name>$tlv$init` data symbol — same emission + * as ELF (shared with define_tls_elf via tls_emit_storage). */ + tls_emit_storage(ob, c, data_sym, data, size, has_nonzero_init, align, relocs, + nrelocs); /* Descriptor in __DATA,__thread_vars: 24 bytes aligned 8. * The user-visible `sym` lives here; the TLVP relocs in code target diff --git a/src/wasm/decode.c b/src/wasm/decode.c @@ -1,4 +1,5 @@ #include "wasm/wasm.h" +#include "wasm/wasm_insn_table.h" typedef struct BinReader { KitCompiler* c; @@ -126,6 +127,19 @@ static WasmValType bin_val_type(BinReader* r, int refs_ok) { return WASM_VAL_I32; } +/* Decode a memarg operand (align/offset/memidx) and append a single memory + * instruction of `kind`. Every WASM_OC_MEMARG opcode decodes identically; only + * the kind differs, so the base loads/stores (0x28..0x3e) and the 0xfe atomics + * share this body, reading their kind from WASM_INSN_TABLE rather than each + * spelling out its own arm. */ +static void decode_memarg_insn(BinReader* r, KitCompiler* c, WasmModule* out, + WasmFunc* f, WasmInsnKind kind) { + uint32_t ma, mi; + uint64_t mo; + bin_memarg(r, &ma, &mo, &mi); + wasm_func_add_mem_insn(c, out, f, kind, ma, mo, mi); +} + /* Decode one instruction opcode body (everything except the body-terminating * 0x0b end) into f's instruction list, advancing *rp. Shared verbatim with the * function-body decode loop so the opcode mapping has a single source of truth; @@ -193,144 +207,6 @@ static void decode_body_insn(BinReader* rp, WasmModule* out, WasmFunc* f, case 0x1b: wasm_func_add_insn(c, out, f, WASM_INSN_SELECT, 0); break; - case 0x28: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD, ma, mo, mi); - } break; - case 0x29: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD, ma, mo, mi); - } break; - case 0x2a: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_F32_LOAD, ma, mo, mi); - } break; - case 0x2b: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_F64_LOAD, ma, mo, mi); - } break; - case 0x2c: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD8_S, ma, mo, mi); - } break; - case 0x2d: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD8_U, ma, mo, mi); - } break; - case 0x2e: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD16_S, ma, mo, mi); - } break; - case 0x2f: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_LOAD16_U, ma, mo, mi); - } break; - case 0x30: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD8_S, ma, mo, mi); - } break; - case 0x31: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD8_U, ma, mo, mi); - } break; - case 0x32: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD16_S, ma, mo, mi); - } break; - case 0x33: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD16_U, ma, mo, mi); - } break; - case 0x34: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD32_S, ma, mo, mi); - } break; - case 0x35: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_LOAD32_U, ma, mo, mi); - } break; - case 0x36: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_STORE, ma, mo, mi); - } break; - case 0x37: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE, ma, mo, mi); - } break; - case 0x38: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_F32_STORE, ma, mo, mi); - } break; - case 0x39: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_F64_STORE, ma, mo, mi); - } break; - case 0x3a: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_STORE8, ma, mo, mi); - } break; - case 0x3b: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_STORE16, ma, mo, mi); - } break; - case 0x3c: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE8, ma, mo, mi); - } break; - case 0x3d: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE16, ma, mo, mi); - } break; - case 0x3e: { - uint32_t ma, mi; - uint64_t mo; - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_STORE32, ma, mo, mi); - } break; case 0x3f: wasm_func_add_insn(c, out, f, WASM_INSN_MEMORY_SIZE, 0); f->insns[f->ninsns - 1u].memidx = bin_uleb(&r); @@ -828,177 +704,33 @@ static void decode_body_insn(BinReader* rp, WasmModule* out, WasmFunc* f, } case 0xfe: { uint32_t sub = bin_uleb(&r); - uint32_t ma, mi; - uint64_t mo; - switch (sub) { - case 0x00: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_MEMORY_ATOMIC_NOTIFY, ma, - mo, mi); - break; - case 0x01: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_WAIT, ma, mo, - mi); - break; - case 0x02: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_WAIT, ma, mo, - mi); - break; - case 0x03: - if (bin_u8(&r) != 0) - wasm_error(c, wasm_loc(0, 0), "wasm: bad atomic.fence"); - wasm_func_add_insn(c, out, f, WASM_INSN_ATOMIC_FENCE, 0); - break; - case 0x10: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_LOAD, ma, mo, - mi); - break; - case 0x11: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_LOAD, ma, mo, - mi); - break; - case 0x12: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_LOAD8_U, ma, - mo, mi); - break; - case 0x13: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_LOAD16_U, ma, - mo, mi); - break; - case 0x14: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_LOAD8_U, ma, - mo, mi); - break; - case 0x15: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_LOAD16_U, ma, - mo, mi); - break; - case 0x16: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_LOAD32_U, ma, - mo, mi); - break; - case 0x17: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_STORE, ma, mo, - mi); - break; - case 0x18: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_STORE, ma, mo, - mi); - break; - case 0x19: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_STORE8, ma, mo, - mi); - break; - case 0x1a: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_STORE16, ma, - mo, mi); - break; - case 0x1b: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_STORE8, ma, mo, - mi); - break; - case 0x1c: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_STORE16, ma, - mo, mi); - break; - case 0x1d: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_STORE32, ma, - mo, mi); - break; - case 0x1e: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_RMW_ADD, ma, - mo, mi); - break; - case 0x1f: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_RMW_ADD, ma, - mo, mi); - break; - case 0x25: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_RMW_SUB, ma, - mo, mi); - break; - case 0x26: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_RMW_SUB, ma, - mo, mi); - break; - case 0x2c: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_RMW_AND, ma, - mo, mi); - break; - case 0x2d: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_RMW_AND, ma, - mo, mi); - break; - case 0x33: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_RMW_OR, ma, mo, - mi); - break; - case 0x34: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_RMW_OR, ma, mo, - mi); - break; - case 0x3a: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_RMW_XOR, ma, - mo, mi); - break; - case 0x3b: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_RMW_XOR, ma, - mo, mi); - break; - case 0x41: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_RMW_XCHG, ma, - mo, mi); - break; - case 0x42: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_RMW_XCHG, ma, - mo, mi); - break; - case 0x48: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I32_ATOMIC_RMW_CMPXCHG, - ma, mo, mi); - break; - case 0x49: - bin_memarg(&r, &ma, &mo, &mi); - wasm_func_add_mem_insn(c, out, f, WASM_INSN_I64_ATOMIC_RMW_CMPXCHG, - ma, mo, mi); - break; - default: - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported threads opcode 0x%x", - sub); + /* atomic.fence is the lone irregular 0xfe op (its operand is a single + * 0x00 byte, not a memarg); every other 0xfe op is a memarg atomic and + * decodes uniformly via the table. */ + if (sub == 0x03u) { + if (bin_u8(&r) != 0) + wasm_error(c, wasm_loc(0, 0), "wasm: bad atomic.fence"); + wasm_func_add_insn(c, out, f, WASM_INSN_ATOMIC_FENCE, 0); + } else { + const WasmInsnInfo* info = + sub <= 0xffu ? wasm_insn_by_byte(WASM_PREFIX_FE, (uint8_t)sub) + : NULL; + if (!info || info->operand_class != WASM_OC_MEMARG) + wasm_error(c, wasm_loc(0, 0), + "wasm: unsupported threads opcode 0x%x", sub); + decode_memarg_insn(&r, c, out, f, (WasmInsnKind)info->kind); } break; } - default: - wasm_error(c, wasm_loc(0, 0), "wasm: unsupported opcode 0x%02x", op); + default: { + /* The base loads/stores (0x28..0x3e) are a contiguous run of memarg ops; + * each decodes identically save for its kind, which the table carries. + * Everything else here is genuinely unknown. */ + const WasmInsnInfo* info = wasm_insn_by_byte(WASM_PREFIX_NONE, op); + if (!info || info->operand_class != WASM_OC_MEMARG) + wasm_error(c, wasm_loc(0, 0), "wasm: unsupported opcode 0x%02x", op); + decode_memarg_insn(&r, c, out, f, (WasmInsnKind)info->kind); + } } *pcontrol_depth = control_depth; diff --git a/src/wasm/wasm_insn_table.c b/src/wasm/wasm_insn_table.c @@ -336,6 +336,15 @@ const WasmInsnInfo* wasm_insn_info(WasmInsnKind kind) { return &WASM_INSN_TABLE[kind]; } +const WasmInsnInfo* wasm_insn_by_byte(WasmInsnPrefix prefix, uint8_t byte) { + for (size_t i = 0; i < WASM_INSN_TABLE_LEN; ++i) { + const WasmInsnInfo* info = &WASM_INSN_TABLE[i]; + if (!info->mnemonic) continue; + if (info->prefix == (uint8_t)prefix && info->byte == byte) return info; + } + return NULL; +} + int wasm_operand_class_has_imm(WasmOperandClass oc) { switch (oc) { case WASM_OC_SLEB: diff --git a/src/wasm/wasm_insn_table.h b/src/wasm/wasm_insn_table.h @@ -11,10 +11,11 @@ * is that map, written once; encode/wat/disasm derive their behaviour from it * by O(1) row lookup or a linear scan, and the mnemonic helper reads a column. * - * Decode keeps its hand-written arms: the per-opcode operand reads are genuinely - * irregular (two index immediates, block-type bytes, br_table vectors, ...) and - * are not worth forcing into a table; it still shares decode_body_insn so the - * byte<->kind mapping lives in exactly one place there too. */ + * Decode keeps hand-written arms for the irregular operand reads (two index + * immediates, block-type bytes, br_table vectors, ...), but the uniform memarg + * loads/stores/atomics derive their byte->kind mapping from this table via + * wasm_insn_by_byte rather than re-listing each opcode. The byte<->kind mapping + * therefore lives in exactly one place. */ #include "wasm/wasm.h" @@ -56,6 +57,11 @@ typedef struct WasmInsnInfo { /* Row for `kind`, or NULL if out of range. O(1). */ const WasmInsnInfo* wasm_insn_info(WasmInsnKind kind); +/* Row for the opcode `byte` in opcode space `prefix` (the byte->kind reverse of + * wasm_insn_info), or NULL if no instruction occupies that slot. Linear scan, + * matching wat.c's mnemonic->kind resolution. This is the decoder's single + * source for the byte<->kind mapping. */ +const WasmInsnInfo* wasm_insn_by_byte(WasmInsnPrefix prefix, uint8_t byte); /* WAT operand-token presence derived from the operand class. */ int wasm_operand_class_has_imm(WasmOperandClass oc); diff --git a/test/parse/run.sh b/test/parse/run.sh @@ -478,6 +478,13 @@ kit_lane_C() { kit_skip "$KIT_NAME/C" "Mach-O underscores C symbol refs; verbatim file-scope asm defines the bare name" return fi + # The C backend replays IR as portable C and has no arch register model, so + # it cannot bind asm operands to machine register classes (aa64 "x"/"y", + # rv64 "cr"/"cf"); cg/asm.c correctly rejects them. Native-only feature. + if [[ "$KIT_BASE" == cg_native_inline_asm_machine_constraints ]]; then + kit_skip "$KIT_NAME/C" "C backend has no machine register model for arch asm constraints" + return + fi if [ $have_c_wrapper -eq 0 ]; then kit_skip "$KIT_NAME/C" "no c-wrapper (host CC failed)" return