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:
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