commit 231ebcd6c7c16800a012415b42d1cabdf343d6bf
parent 796293a771df0878c28f90f91e19fca054054552
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 12 Jun 2026 10:37:46 -0700
feat(test): add reproducible real-world C ecosystem build gate
Adds test/ecosystem/ + scripts/ecosystem.sh: a differential gate that proves
kit builds and runs real upstream C projects and matches clang byte-for-byte.
- scripts/ecosystem.sh fetches pinned, sha256-verified upstream releases into
the kit cache dir (~/.cache/kit/ecosystem) and extracts them, so the gate
runs fully offline. Per-project metadata is the single source of truth in
test/ecosystem/recipes/<name>.recipe.
- test/ecosystem/run.sh compiles each TU with `kit cc`, archives with `kit ar`,
links a driver (the project's own shell/interpreter, or a use/<name>.c that
exercises real functionality), runs it, and diffs the deterministic output
against a checked-in golden AND against the same program built with clang.
Every project runs at -O0 and -O1.
- make targets: provision-ecosystem (network fetch), test-ecosystem (offline,
opt-in; needs a host clang for the differential).
Green at -O0 and -O1, byte-identical to clang: cJSON, Lua, LZ4, miniz,
tinyexpr; SQLite green at -O0. The gate surfaced two real kit bugs, kept RED
on purpose (not xfailed) with artifacts preserved to build/ecosystem-fail/ and
documented in test/ecosystem/known_bugs/:
A. MCEmitter: label NNNN placed twice — compiling yyjson.c at -O0.
B. `kit cc src.c archive.a` at -O1 drops a local .Lkit_ro.N symbol
(one-shot compile+link; pre-compiling the driver to .o works).
Diffstat:
28 files changed, 1125 insertions(+), 12 deletions(-)
diff --git a/doc/plan/RELEASE.md b/doc/plan/RELEASE.md
@@ -231,17 +231,29 @@ Verified native/VM run signoff:
## C ecosystem gates
-- [ ] Add a small, curated `examples/` or `test/ecosystem/` suite that proves kit
+The reproducible harness landed: `test/ecosystem/` + `scripts/ecosystem.sh`,
+driven by `make provision-ecosystem` (network: fetch + sha256-verify + extract
+pinned upstream sources into the kit cache dir) and `make test-ecosystem`
+(offline: build each project's TUs with `kit cc`, archive with `kit ar`, link a
+driver, run it, and diff the output against a checked-in golden AND against the
+same program built with clang). Both `-O0` and `-O1` run for every project.
+Green at both levels and byte-identical to clang: cJSON, Lua, LZ4, miniz,
+tinyexpr (and SQLite at `-O0`). Two real kit bugs are kept RED on purpose and
+documented in `test/ecosystem/known_bugs/`: (A) `MCEmitter: label … placed
+twice` compiling yyjson at `-O0`; (B) `kit cc src.c archive.a` at `-O1` drops a
+local `.Lkit_ro.N` symbol (hits SQLite + yyjson at `-O1`).
+
+- [x] Add a small, curated `examples/` or `test/ecosystem/` suite that proves kit
can compile real C projects with ordinary headers, build scripts, archives,
and hosted system libraries.
-- [ ] Keep the suite deterministic: pinned upstream versions, checked hashes,
+- [x] Keep the suite deterministic: pinned upstream versions, checked hashes,
patch files kept minimal and documented, no network access in default test
runs.
-- [ ] Add a source-built library gate for SQLite: compile the amalgamation,
+- [x] Add a source-built library gate for SQLite: compile the amalgamation,
archive `libsqlite3.a`, build the shell or a focused smoke app, and validate
- basic SQL execution.
-- [ ] Add a source-built compression/library gate such as zlib or libpng to cover
- common configure-style C and archive linking.
+ basic SQL execution. (Green at `-O0`; `-O1` link kept red — known bug B.)
+- [x] Add a source-built compression/library gate such as zlib or libpng to cover
+ common configure-style C and archive linking. (miniz + LZ4, both `-O0`/`-O1`.)
- [ ] Add a hosted SDL2 gate: compile a minimal C app and link it through the
platform mechanism (`pkg-config`/`sdl2-config`, framework, or import
library), with an offscreen/headless smoke mode where possible.
@@ -250,15 +262,17 @@ Verified native/VM run signoff:
- [ ] Add a macOS framework gate, preferably a pure-C CoreFoundation smoke, to
prove `-framework` and `.tbd` system-library resolution.
- [ ] Add Linux/FreeBSD system-library link gates for `-lm`, `-ldl`, `-lpthread`,
- and one package-discovered library.
+ and one package-discovered library. (Lua already links `-lm`; a dedicated
+ gate is still pending.)
- [ ] Add Windows hosted link gates for system/import libraries used by the
release support set.
-- [ ] Run the ecosystem suite at `-O0` and `-O1`; add an LTO lane for at least
- SQLite once LTO lands.
-- [ ] Decide which gates are required in default release CI and which are
+- [x] Run the ecosystem suite at `-O0` and `-O1`; add an LTO lane for at least
+ SQLite once LTO lands. (Both opt levels run; LTO lane still TODO.)
+- [x] Decide which gates are required in default release CI and which are
opt-in because they require host packages or graphics/display access.
-- [ ] Document the examples as supported smoke builds, with exact commands users
- can run after installing kit.
+ (Opt-in: needs provisioned sources + a host clang; not in DEFAULT_TEST_TARGETS.)
+- [x] Document the examples as supported smoke builds, with exact commands users
+ can run after installing kit. (`test/ecosystem/README.md`.)
## Release validation
diff --git a/mk/test.mk b/mk/test.mk
@@ -79,6 +79,7 @@ TEST_TARGETS = \
test-hash \
test-driver-strip \
test-dwarf \
+ test-ecosystem \
test-elf \
test-extlink \
test-emu \
@@ -313,6 +314,22 @@ test-driver-pkg: bin
test-extlink: bin
@KIT=$(abspath $(BIN)) sh test/extlink/run.sh
+# test-ecosystem: real-world C build gate. Builds pinned upstream projects
+# (SQLite, Lua, cJSON, LZ4, miniz, tinyexpr, yyjson) from the kit cache dir with
+# kit, links a driver that exercises real functionality, and asserts the output
+# against a checked-in golden AND against the same program built with clang.
+# OPT-IN (not in DEFAULT_TEST_TARGETS): it needs sources provisioned first and a
+# host clang for the differential. Provision once with `make provision-ecosystem`
+# (network); the gate then runs fully offline. Known kit bugs are kept RED on
+# purpose (see test/ecosystem/known_bugs/); failing artifacts are preserved
+# under build/ecosystem-fail/.
+.PHONY: test-ecosystem provision-ecosystem
+provision-ecosystem:
+ @sh scripts/ecosystem.sh fetch all
+
+test-ecosystem: bin
+ @KIT=$(abspath $(BIN)) sh test/ecosystem/run.sh
+
# test-dist: one-command verification of the dist subsystem (content-addressed
# store + signed packaging). Runs the CAS and pkg driver corpora, which cover
# round-trip, integrity/tamper detection, trust store, and the read-side
diff --git a/scripts/ecosystem.sh b/scripts/ecosystem.sh
@@ -0,0 +1,162 @@
+#!/usr/bin/env bash
+# scripts/ecosystem.sh — provision real-world C projects for the ecosystem gate.
+#
+# Fetches pinned, hash-verified upstream releases into the kit cache dir and
+# extracts them, so the integration tests (test/ecosystem/run.sh) run fully
+# offline against a reproducible source tree. The per-project metadata (URL,
+# version, sha256, layout) is the single source of truth in
+# test/ecosystem/recipes/<name>.recipe — this script just drives it.
+#
+# Cache dir resolution (matches driver/env's cache_dir convention):
+# $KIT_ECO_CACHE (explicit override), else
+# $XDG_CACHE_HOME/kit/ecosystem, else
+# $HOME/.cache/kit/ecosystem, else
+# <repo>/build/kit-cache/ecosystem
+#
+# Commands:
+# list print recipe names, one per line
+# cachedir print the resolved cache dir
+# status show provisioned / missing per project
+# entrydir <name> print the cache entry dir for <name>
+# srcdir <name> print the source root (entry/<ECO_SRCDIR>);
+# empty + exit 1 if not provisioned
+# fetch <name>|all download + verify sha256 + extract
+# clean <name>|all remove cache entries
+#
+# env: KIT_ECO_CACHE (cache override). No network except `fetch`.
+
+set -eu
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+RECIPE_DIR="$ROOT/test/ecosystem/recipes"
+
+eco_cachedir() {
+ if [ -n "${KIT_ECO_CACHE:-}" ]; then printf '%s\n' "$KIT_ECO_CACHE"; return; fi
+ if [ -n "${XDG_CACHE_HOME:-}" ]; then printf '%s/kit/ecosystem\n' "$XDG_CACHE_HOME"; return; fi
+ if [ -n "${HOME:-}" ]; then printf '%s/.cache/kit/ecosystem\n' "$HOME"; return; fi
+ printf '%s/build/kit-cache/ecosystem\n' "$ROOT"
+}
+CACHE="$(eco_cachedir)"
+
+die() { printf 'ecosystem: %s\n' "$*" >&2; exit 1; }
+
+recipes() {
+ for r in "$RECIPE_DIR"/*.recipe; do
+ [ -e "$r" ] || continue
+ basename "$r" .recipe
+ done
+}
+
+recipe_path() {
+ local p="$RECIPE_DIR/$1.recipe"
+ [ -f "$p" ] || die "unknown project '$1' (see: $0 list)"
+ printf '%s\n' "$p"
+}
+
+# Load a recipe's provisioning vars into the current shell. (Functions are
+# defined too but unused here.) Sets ECO_VERSION/URL/SHA256/SRCDIR.
+load_recipe() {
+ ECO_VERSION=; ECO_URL=; ECO_SHA256=; ECO_SRCDIR=.
+ # shellcheck disable=SC1090
+ . "$(recipe_path "$1")"
+ [ -n "$ECO_URL" ] || die "$1: ECO_URL unset"
+ [ -n "$ECO_SHA256" ] || die "$1: ECO_SHA256 unset"
+}
+
+entrydir() { load_recipe "$1"; printf '%s/%s-%s\n' "$CACHE" "$1" "$ECO_VERSION"; }
+
+srcdir() {
+ local e; e="$(entrydir "$1")"
+ load_recipe "$1"
+ local s="$e/$ECO_SRCDIR"
+ if [ -d "$s" ]; then printf '%s\n' "$s"; else return 1; fi
+}
+
+sha256_of() {
+ if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1
+ elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1
+ else die "no sha256sum/shasum available"; fi
+}
+
+fetch_one() {
+ local name="$1"
+ load_recipe "$name"
+ local entry; entry="$(entrydir "$name")"
+ if [ -d "$entry/$ECO_SRCDIR" ]; then
+ printf ' %-10s already provisioned (%s)\n' "$name" "$ECO_VERSION"
+ return 0
+ fi
+ command -v curl >/dev/null 2>&1 || die "curl required for fetch"
+
+ local tmp; tmp="$(mktemp -d "${TMPDIR:-/tmp}/kit-eco-$name.XXXXXX")"
+ local ar="$tmp/archive"
+
+ printf ' %-10s fetching %s\n' "$name" "$ECO_URL"
+ curl -fsSL --retry 3 -o "$ar" "$ECO_URL" || die "$name: download failed"
+
+ local got; got="$(sha256_of "$ar")"
+ if [ "$got" != "$ECO_SHA256" ]; then
+ die "$name: sha256 mismatch
+ expected $ECO_SHA256
+ got $got"
+ fi
+
+ rm -rf "$entry"; mkdir -p "$entry"
+ case "$ECO_URL" in
+ *.zip)
+ command -v unzip >/dev/null 2>&1 || die "unzip required for $name"
+ unzip -q -o "$ar" -d "$entry" ;;
+ *.tar.gz|*.tgz)
+ tar xzf "$ar" -C "$entry" ;;
+ *) die "$name: unknown archive type for $ECO_URL" ;;
+ esac
+
+ rm -rf "$tmp"
+ [ -d "$entry/$ECO_SRCDIR" ] || die "$name: ECO_SRCDIR '$ECO_SRCDIR' not found after extract"
+ printf ' %-10s OK %s -> %s\n' "$name" "$ECO_VERSION" "$entry/$ECO_SRCDIR"
+}
+
+cmd_fetch() {
+ mkdir -p "$CACHE"
+ if [ "${1:-all}" = all ]; then
+ for n in $(recipes); do fetch_one "$n"; done
+ else
+ fetch_one "$1"
+ fi
+}
+
+cmd_clean() {
+ if [ "${1:-all}" = all ]; then
+ for n in $(recipes); do rm -rf "$(entrydir "$n")"; done
+ printf 'cleaned all ecosystem cache entries under %s\n' "$CACHE"
+ else
+ rm -rf "$(entrydir "$1")"; printf 'cleaned %s\n' "$1"
+ fi
+}
+
+cmd_status() {
+ printf 'cache: %s\n' "$CACHE"
+ for n in $(recipes); do
+ if srcdir "$n" >/dev/null 2>&1; then
+ printf ' [x] %-10s %s\n' "$n" "$ECO_VERSION"
+ else
+ printf ' [ ] %-10s %s (run: %s fetch %s)\n' "$n" "$ECO_VERSION" "$0" "$n"
+ fi
+ done
+}
+
+main() {
+ local cmd="${1:-status}"; shift || true
+ case "$cmd" in
+ list|recipes) recipes ;;
+ cachedir) printf '%s\n' "$CACHE" ;;
+ status) cmd_status ;;
+ entrydir) [ $# -ge 1 ] || die "entrydir <name>"; entrydir "$1" ;;
+ srcdir) [ $# -ge 1 ] || die "srcdir <name>"; srcdir "$1" || { printf '' ; exit 1; } ;;
+ fetch) cmd_fetch "${1:-all}" ;;
+ clean) cmd_clean "${1:-all}" ;;
+ *) die "unknown command '$cmd' (list|cachedir|status|entrydir|srcdir|fetch|clean)" ;;
+ esac
+}
+
+main "$@"
diff --git a/test/ecosystem/README.md b/test/ecosystem/README.md
@@ -0,0 +1,61 @@
+# Ecosystem gate — building real-world C with kit
+
+This suite proves kit can compile, archive, link, and **run** real upstream C
+projects, and that the results match a reference clang build byte-for-byte.
+
+It is intentionally a differential, reproducible gate:
+
+- **Reproducible** — sources are pinned upstream releases (URL + sha256) fetched
+ into the kit cache dir, never vendored into the repo. Provision once with
+ network access; the gate then runs fully offline against the cache.
+- **Real usage** — each project is built the way it is actually consumed: every
+ translation unit is compiled with `kit cc`, archived into a static library
+ with `kit ar`, and a driver (the project's own shell/interpreter, or a small
+ `use/<name>.c`) is linked against it and executed.
+- **Differential** — the program's deterministic output is asserted two ways:
+ against a checked-in golden (`expected/<name>.txt`) **and** against the same
+ program built with the host `clang` (the differential oracle).
+- **Both opt levels** — every project runs at `-O0` and `-O1`.
+
+## Run it
+
+```sh
+make provision-ecosystem # fetch + verify + extract pinned sources (network)
+make test-ecosystem # build with kit + clang, run, diff (offline)
+```
+
+Or directly, with finer control:
+
+```sh
+scripts/ecosystem.sh status # what's provisioned
+scripts/ecosystem.sh fetch sqlite # fetch one project
+test/ecosystem/run.sh sqlite lua # run only these
+KIT_ECO_OPT=O0 test/ecosystem/run.sh # one opt level
+test/ecosystem/run.sh --update # refresh goldens (from clang)
+```
+
+## Projects
+
+| project | what the driver exercises |
+|----------|---------------------------|
+| sqlite | shell built from the amalgamation runs SQL: joins, aggregates, CTEs, window functions, JSON |
+| lua | ~30-TU static lib + interpreter runs a script: GC, closures, coroutines, `string.format`, integer/float |
+| cJSON | parse / navigate / build / serialize JSON |
+| lz4 | block compress/decompress round-trips |
+| miniz | deflate/inflate round-trips + CRC-32 / Adler-32 |
+| tinyexpr | expression compile + variable binding |
+| yyjson | read / navigate / mutate / write JSON |
+
+## Adding a project
+
+Drop a `recipes/<name>.recipe` (contract documented in `recipes/sqlite.recipe`):
+pin `ECO_URL` + `ECO_SHA256`, point `ECO_SRCDIR` at the source root, define
+`eco_lib_srcs` and `eco_run`, and add a driver under `use/` if the project has no
+suitable `main`. Then `scripts/ecosystem.sh fetch <name>` and
+`test/ecosystem/run.sh --update <name>` to seed the golden.
+
+## Known bugs (kept red on purpose)
+
+Failures are **not** xfailed or worked around — see `known_bugs/`. The gate stays
+red until they are fixed, and each failing build's artifacts are preserved under
+`build/ecosystem-fail/` for investigation.
diff --git a/test/ecosystem/expected/cjson.txt b/test/ecosystem/expected/cjson.txt
@@ -0,0 +1,4 @@
+name=kit ok=1 v=42 nlen=3
+nums-sum=6
+built={"lib":"cJSON","answer":42,"seq":[1,4,9,16]}
+reparse-answer=42
diff --git a/test/ecosystem/expected/lua.txt b/test/ecosystem/expected/lua.txt
@@ -0,0 +1,7 @@
+fmt 42 2.500 ababab
+sorted: 1,2,3,5,6,7,9,10
+counter: 123
+coro: 1,4,9,16
+pcall: false boom
+math 3 1024 1.4142
+type: integer/float
diff --git a/test/ecosystem/expected/lz4.txt b/test/ecosystem/expected/lz4.txt
@@ -0,0 +1,3 @@
+text in=43 comp=44 out=43 match=1
+repeat in=1024 comp=21 out=1024 match=1
+versionstr=1.10.0
diff --git a/test/ecosystem/expected/miniz.txt b/test/ecosystem/expected/miniz.txt
@@ -0,0 +1,3 @@
+in=66 comp=62 out=66 match=1
+crc32=ddd84c0a
+adler-of-deflate=1599544165
diff --git a/test/ecosystem/expected/sqlite.txt b/test/ecosystem/expected/sqlite.txt
@@ -0,0 +1,14 @@
+eng|2|220|110.0
+ops|2|200|100.0
+sales|1|80|80.0
+alice|120|132
+bob|100|110
+carol|90|94
+dave|110|115
+1,2,3,4,5,6,7,8,9,10|55
+KIT|8|00042|9
+erin|80|80
+carol|90|170
+bob|100|270
+dave|110|380
+alice|120|500
diff --git a/test/ecosystem/expected/tinyexpr.txt b/test/ecosystem/expected/tinyexpr.txt
@@ -0,0 +1,7 @@
+expr[0]=5 err=0
+expr[1]=9 err=0
+expr[2]=1 err=0
+expr[3]=nan err=3
+f(1,2)=5
+f(2,3)=13
+f(3,4)=25
diff --git a/test/ecosystem/known_bugs/README.md b/test/ecosystem/known_bugs/README.md
@@ -0,0 +1,28 @@
+# Ecosystem gate — known kit bugs (kept RED on purpose)
+
+The real-world ecosystem gate (`test/ecosystem/run.sh`, `make test-ecosystem`)
+surfaced genuine kit defects while building upstream C projects. We deliberately
+**do not** xfail or work around them: the gate stays red until they are fixed,
+so they remain visible and easy to dig into.
+
+When the gate fails, the failing build's artifacts are preserved under
+`build/ecosystem-fail/<project>-<opt>-<stage>/`:
+
+- `build.err` — captured compiler/linker stderr
+- `cmds.sh` — the exact kit/ar/clang commands that ran, in order
+- `srcs` — the library translation-unit list
+- `out`, `run.err`, `golden.expected`, `clang.out` — for output mismatches
+
+## Open bugs
+
+| id | file | trigger | symptom |
+|----|------|---------|---------|
+| A | [yyjson-write-label.md](yyjson-write-label.md) | yyjson.c at **-O0** | `MCEmitter: label NNNN placed twice` (codegen abort) |
+| B | [sqlite-o1-lkit-ro.md](sqlite-o1-lkit-ro.md) | `kit cc src.c archive.a` at **-O1** | `undefined reference to '.Lkit_ro.N'` (one-shot compile+link drops a local rodata symbol) |
+
+Bug B hits both SQLite and yyjson at -O1; Bug A hits yyjson at -O0. The other
+five projects (cJSON, LZ4, miniz, tinyexpr, Lua) are green at both -O0 and -O1
+and match clang byte-for-byte.
+
+Each bug doc has a self-contained reproduction against the provisioned cache
+(`make provision-ecosystem` first).
diff --git a/test/ecosystem/known_bugs/sqlite-o1-lkit-ro.md b/test/ecosystem/known_bugs/sqlite-o1-lkit-ro.md
@@ -0,0 +1,69 @@
+# Bug B — `-O1` one-shot compile+link against an archive drops a local `.Lkit_ro.N`
+
+**Status:** open. Kept red by the ecosystem gate (`sqlite:O1:build`,
+`yyjson:O1:build`).
+**Component:** linker / one-shot compile+link object handling at -O1
+(`src/link/`, interaction with the in-invocation compiled object's local
+`.Lkit_ro` rodata symbols).
+**Severity:** `kit cc main.c libfoo.a -o app` at -O1 fails on real archives.
+
+## Symptom
+
+```
+fatal: link: undefined reference to '.Lkit_ro.119' # sqlite (shell.c + libsqlite3.a)
+fatal: link: undefined reference to '.Lkit_ro.0' # yyjson (use_yyjson.c + libyyjson.a)
+```
+
+`.Lkit_ro.N` are kit's **local** (file-scope) read-only-data labels, numbered
+from 0 per object. The reference is reported undefined even though the symbol
+*is* defined locally in the archive member:
+
+```
+$ kit nm sqlite3.o | grep '.Lkit_ro.119'
+0000000000007ad5 d .Lkit_ro.119 # 'd' = local data, present
+```
+
+## Reproduce
+
+```sh
+make provision-ecosystem # once (network)
+KIT=build/kit
+SDK="$(xcrun --sdk macosx --show-sdk-path)"
+SQ="$(sh scripts/ecosystem.sh srcdir sqlite)"
+DEFS="-DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION"
+
+$KIT cc -O1 --sysroot "$SDK" $DEFS -I "$SQ" -c "$SQ/sqlite3.c" -o /tmp/sqlite3.o
+$KIT ar rcs /tmp/libsqlite3.a /tmp/sqlite3.o
+
+# FAILS: one-shot — compile the driver source AND link the archive together:
+$KIT cc -O1 --sysroot "$SDK" $DEFS -I "$SQ" "$SQ/shell.c" /tmp/libsqlite3.a -lc -o /tmp/sqlite3
+
+# WORKS: pre-compile the driver to .o first, then link:
+$KIT cc -O1 --sysroot "$SDK" $DEFS -I "$SQ" -c "$SQ/shell.c" -o /tmp/shell.o
+$KIT cc -O1 --sysroot "$SDK" $DEFS /tmp/shell.o /tmp/libsqlite3.a -lc -o /tmp/sqlite3 # exit 0
+```
+
+## What is and isn't affected
+
+- **-O0 one-shot links fine** (sqlite is green at -O0 and matches clang).
+- **-O1 separate compile-then-link works** (see the WORKS case above) — so the
+ archive member and its `.Lkit_ro` symbols are individually sound.
+- The break is specific to the **one-shot path**: when `kit cc` compiles a
+ driver source *in the same invocation* as it links an archive at -O1. The
+ just-compiled driver object also emits `.Lkit_ro.N` numbered from 0, so the
+ leading suspicion is a **collision between the in-invocation object's local
+ `.Lkit_ro.N` labels and the archive member's**, leaving one reference bound to
+ a symbol that the resolver then can't find.
+- Related history: kit has had `.Lkit_ro`/`.Lkit_jt` local-label
+ globalize/resolution issues before (assembler tombstone-globalize for FreeBSD
+ bootstrap). Worth checking whether one-shot link reuses local-symbol numbering
+ state across the compiled-here object and the pulled-in archive member.
+
+## Notes for digging
+
+- A tiny two-file repro (`a.c` with one `static const char[]`, `m.c` calling it,
+ archived) did **not** reproduce — the trigger needs many `.Lkit_ro` symbols
+ and/or specific numbering overlap, so reduce from sqlite/yyjson rather than
+ from scratch.
+- The ecosystem harness links the driver one-shot on purpose; keeping that path
+ is what holds this bug red.
diff --git a/test/ecosystem/known_bugs/yyjson-write-label.md b/test/ecosystem/known_bugs/yyjson-write-label.md
@@ -0,0 +1,43 @@
+# Bug A — `MCEmitter: label NNNN placed twice` compiling yyjson at -O0
+
+**Status:** open. Kept red by the ecosystem gate (`yyjson:O0:build`).
+**Component:** codegen / machine-code emitter (`src/arch/*/` MCEmitter label pass).
+**Severity:** kit cannot compile yyjson at -O0 at all.
+
+## Symptom
+
+```
+yyjson.c:9041:1: fatal: MCEmitter: label 6020 placed twice
+```
+
+The line (and the internal label number) move slightly with the input, but it
+always lands on a `fail_*:` epilogue label of a large writer function —
+`yyjson_write_single` (~yyjson.c:8947–9044) or `yyjson_write_minify`
+(~9053–9216). These functions are big `switch` bodies whose arms all `goto` a
+shared handful of `fail_alloc:`/`fail_type:`/`fail_num:`/`fail_str:` epilogues
+(via the `return_err` macro). The emitter places the *same* internal label
+twice — i.e. a basic-block/label-dedup defect when many predecessors branch to
+one shared, late label in a large function.
+
+## Reproduce
+
+```sh
+make provision-ecosystem # once (network)
+KIT=build/kit
+SDK="$(xcrun --sdk macosx --show-sdk-path)"
+YY="$(sh scripts/ecosystem.sh srcdir yyjson)"
+
+# Fails at -O0:
+$KIT cc -O0 --sysroot "$SDK" -I "$YY" -c "$YY/yyjson.c" -o /tmp/yyjson.o
+```
+
+## Notes for digging
+
+- **-O1 compiles this TU fine** — the bug is specific to the -O0 native emitter
+ path, not the optimizer. (At -O1 yyjson then trips Bug B at link instead.)
+- The internal label is an MCEmitter label id, not a source label, so this is in
+ the backend label/branch fixup, not the C front end.
+- Likely the same shared-late-label shape can be reduced to a small synthetic
+ function (big `switch`, every arm `goto fail;`, one `fail:` at the end). A
+ minimal reducer is still TODO — drop it next to this doc as `yyjson_label.c`
+ once found.
diff --git a/test/ecosystem/recipes/cjson.recipe b/test/ecosystem/recipes/cjson.recipe
@@ -0,0 +1,15 @@
+# test/ecosystem/recipes/cjson.recipe — real-world C build gate for cJSON.
+# Contract documented in sqlite.recipe.
+
+ECO_VERSION=1.7.18
+ECO_URL="https://github.com/DaveGamble/cJSON/archive/refs/tags/v1.7.18.tar.gz"
+ECO_SHA256=3aa806844a03442c00769b83e99970be70fbef03735ff898f4811dd03b9f5ee5
+ECO_SRCDIR=cJSON-1.7.18
+ECO_INCLUDES="."
+ECO_DEFINES=""
+ECO_LIBS=""
+ECO_DRIVER=use/use_cjson.c
+
+eco_lib_srcs() { echo "$ECO_SRC/cJSON.c"; }
+
+eco_run() { "$1"; }
diff --git a/test/ecosystem/recipes/lua.recipe b/test/ecosystem/recipes/lua.recipe
@@ -0,0 +1,25 @@
+# test/ecosystem/recipes/lua.recipe — real-world C build gate for Lua.
+# Contract documented in sqlite.recipe.
+#
+# Lua is the strongest single gate here: ~30 TUs archived into a static lib and
+# linked into the stock interpreter, which then runs a script exercising the GC,
+# string formatting (host snprintf), tables, closures, coroutines, and math.
+
+ECO_VERSION=5.4.7
+ECO_URL="https://www.lua.org/ftp/lua-5.4.7.tar.gz"
+ECO_SHA256=9fbf5e28ef86c69858f6d3d34eccc32e911c1a28b4120ff3e84aaa70cfbf1e30
+ECO_SRCDIR=lua-5.4.7/src
+ECO_INCLUDES="."
+# Plain ANSI build (no LUA_USE_*): no dynamic loading, fully portable across the
+# hosted support set. os/io/string/math all still work.
+ECO_DEFINES=""
+ECO_LIBS="-lm"
+ECO_DRIVER=lua.c
+
+# The library is every src/*.c except the two main()-bearing tools (the
+# interpreter lua.c, which is our driver, and the compiler luac.c).
+eco_lib_srcs() {
+ find "$ECO_SRC" -maxdepth 1 -name '*.c' ! -name 'lua.c' ! -name 'luac.c'
+}
+
+eco_run() { "$1" "$ECO_DIR/scripts/hello.lua"; }
diff --git a/test/ecosystem/recipes/lz4.recipe b/test/ecosystem/recipes/lz4.recipe
@@ -0,0 +1,15 @@
+# test/ecosystem/recipes/lz4.recipe — real-world C build gate for LZ4.
+# Contract documented in sqlite.recipe.
+
+ECO_VERSION=1.10.0
+ECO_URL="https://github.com/lz4/lz4/releases/download/v1.10.0/lz4-1.10.0.tar.gz"
+ECO_SHA256=537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b
+ECO_SRCDIR=lz4-1.10.0/lib
+ECO_INCLUDES="."
+ECO_DEFINES=""
+ECO_LIBS=""
+ECO_DRIVER=use/use_lz4.c
+
+eco_lib_srcs() { echo "$ECO_SRC/lz4.c"; }
+
+eco_run() { "$1"; }
diff --git a/test/ecosystem/recipes/miniz.recipe b/test/ecosystem/recipes/miniz.recipe
@@ -0,0 +1,18 @@
+# test/ecosystem/recipes/miniz.recipe — real-world C build gate for miniz.
+# Contract documented in sqlite.recipe.
+#
+# The 3.0.2 release zip is the amalgamated single-TU distribution (miniz.c +
+# miniz.h at the archive root), so it extracts flat — ECO_SRCDIR is ".".
+
+ECO_VERSION=3.0.2
+ECO_URL="https://github.com/richgel999/miniz/releases/download/3.0.2/miniz-3.0.2.zip"
+ECO_SHA256=ada38db0b703a56d3dd6d57bf84a9c5d664921d870d8fea4db153979fb5332c5
+ECO_SRCDIR=.
+ECO_INCLUDES="."
+ECO_DEFINES=""
+ECO_LIBS=""
+ECO_DRIVER=use/use_miniz.c
+
+eco_lib_srcs() { echo "$ECO_SRC/miniz.c"; }
+
+eco_run() { "$1"; }
diff --git a/test/ecosystem/recipes/sqlite.recipe b/test/ecosystem/recipes/sqlite.recipe
@@ -0,0 +1,46 @@
+# test/ecosystem/recipes/sqlite.recipe — real-world C build gate for SQLite.
+#
+# RECIPE CONTRACT (sourced by scripts/ecosystem.sh for fetch, and by
+# test/ecosystem/run.sh for build+run). A recipe is a POSIX-sh fragment that
+# sets the variables below and defines two functions. The harness exports
+# $ECO_SRC (absolute path to the source root in the cache) and $ECO_DIR
+# (absolute path to test/ecosystem) before sourcing.
+#
+# ECO_VERSION upstream version/tag (informational + cache key)
+# ECO_URL pinned download URL
+# ECO_SHA256 sha256 of the downloaded archive (provisioning is hash-gated)
+# ECO_SRCDIR source root relative to the extracted cache entry ("." if flat)
+# ECO_INCLUDES space-separated -I dirs, relative to the source root
+# ECO_DEFINES extra -D flags applied to every translation unit
+# ECO_LIBS extra link libraries (e.g. "-lm")
+# ECO_DRIVER driver main TU: "use/<f>.c" resolves under test/ecosystem,
+# anything else is relative to the source root (the project's
+# own main, e.g. SQLite's shell.c)
+# ECO_KNOWN_RED "1" if kit currently miscompiles this project (recorded xfail)
+#
+# eco_lib_srcs print the library's translation units (abs paths), 1/line
+# eco_run "$APP" run the built app deterministically; stdout is the oracle
+#
+# Determinism rule: eco_run output must be byte-identical regardless of compiler,
+# host RNG, time, pointer values, or hash-table iteration order. The harness
+# diffs it against a checked-in golden AND against a clang-built reference.
+
+ECO_VERSION=3.50.2
+ECO_URL="https://www.sqlite.org/2025/sqlite-amalgamation-3500200.zip"
+ECO_SHA256=387991de2834b5da2894119ff4173a9ea0779ea55ebcf53d9a40b24d1dc2484e
+ECO_SRCDIR=sqlite-amalgamation-3500200
+ECO_INCLUDES="."
+ECO_DEFINES="-DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION"
+ECO_LIBS=""
+ECO_DRIVER=shell.c
+# KNOWN-RED at -O1 only (O0 builds + matches clang): the final link fails with
+# "undefined reference to '.Lkit_ro.119'"
+# — a kit -O1 local rodata-label (.Lkit_ro) resolution bug. Kept red on purpose.
+# Details + repro: test/ecosystem/known_bugs/sqlite-o1-lkit-ro.md.
+ECO_BUG="known_bugs/sqlite-o1-lkit-ro.md"
+
+eco_lib_srcs() { echo "$ECO_SRC/sqlite3.c"; }
+
+# Build the amalgamation into the stock shell and run a deterministic SQL
+# script through it (batch mode; piped stdin suppresses the interactive banner).
+eco_run() { "$1" ":memory:" < "$ECO_DIR/scripts/sqlite.sql"; }
diff --git a/test/ecosystem/recipes/tinyexpr.recipe b/test/ecosystem/recipes/tinyexpr.recipe
@@ -0,0 +1,17 @@
+# test/ecosystem/recipes/tinyexpr.recipe — real-world C build gate for tinyexpr.
+# Contract documented in sqlite.recipe.
+#
+# tinyexpr has no release tarballs, so we pin a specific commit archive.
+
+ECO_VERSION=ffb0d41
+ECO_URL="https://github.com/codeplea/tinyexpr/archive/ffb0d41b13e5f8d318db95feb071c220c134fe70.tar.gz"
+ECO_SHA256=73263b6f40f3a97759972fd916368db054d66d2c3a6ba78052fbd8f9d324029c
+ECO_SRCDIR=tinyexpr-ffb0d41b13e5f8d318db95feb071c220c134fe70
+ECO_INCLUDES="."
+ECO_DEFINES=""
+ECO_LIBS="-lm"
+ECO_DRIVER=use/use_tinyexpr.c
+
+eco_lib_srcs() { echo "$ECO_SRC/tinyexpr.c"; }
+
+eco_run() { "$1"; }
diff --git a/test/ecosystem/recipes/yyjson.recipe b/test/ecosystem/recipes/yyjson.recipe
@@ -0,0 +1,23 @@
+# test/ecosystem/recipes/yyjson.recipe — real-world C build gate for yyjson.
+# Contract documented in sqlite.recipe.
+#
+# KNOWN-RED (kept red on purpose — do not xfail): kit aborts compiling
+# yyjson.c at the write path with "MCEmitter: label NNNN placed twice"
+# (reproduces at -O0 AND -O1 with `-c` on yyjson_write_minify, the function near
+# yyjson.c:9216 with its many `goto fail_*` epilogues). ECO_BUG only annotates
+# the failure; the gate stays red until the codegen bug is fixed. Details +
+# repro: test/ecosystem/known_bugs/yyjson-write-label.md.
+
+ECO_VERSION=0.12.0
+ECO_URL="https://github.com/ibireme/yyjson/archive/refs/tags/0.12.0.tar.gz"
+ECO_SHA256=b16246f617b2a136c78d73e5e2647c6f1de1313e46678062985bdcf1f40bb75d
+ECO_SRCDIR=yyjson-0.12.0/src
+ECO_INCLUDES="."
+ECO_DEFINES=""
+ECO_LIBS=""
+ECO_DRIVER=use/use_yyjson.c
+ECO_BUG="known_bugs/yyjson-write-label.md"
+
+eco_lib_srcs() { echo "$ECO_SRC/yyjson.c"; }
+
+eco_run() { "$1"; }
diff --git a/test/ecosystem/run.sh b/test/ecosystem/run.sh
@@ -0,0 +1,252 @@
+#!/usr/bin/env bash
+# test/ecosystem/run.sh — the C ecosystem gate.
+#
+# Builds real-world C projects (SQLite, Lua, cJSON, LZ4, miniz, tinyexpr, ...)
+# from pinned upstream sources with kit: compile each translation unit, archive
+# into a static library with `kit ar`, link a driver that exercises real
+# functionality, run it, and assert the output two ways:
+#
+# <name>:<opt>:golden kit output == checked-in expected/<name>.txt
+# <name>:<opt>:vs-clang kit output == output of the SAME program built with
+# clang on this host (differential oracle)
+#
+# Sources come from the kit cache dir, provisioned offline first via
+# scripts/ecosystem.sh fetch all (or: make provision-ecosystem)
+# Projects that are not provisioned are SKIPPED with a hint, never failed.
+#
+# Per-project metadata + build/run recipe: test/ecosystem/recipes/<name>.recipe.
+#
+# Usage:
+# test/ecosystem/run.sh [name ...] run all, or only the named projects
+# test/ecosystem/run.sh --update [...] refresh expected/<name>.txt goldens
+# (from the clang build, else the kit build)
+# env:
+# KIT kit binary (default build/kit)
+# KIT_ECO_OPT space-separated opt levels (default "O0 O1")
+# KIT_ECO_NO_CLANG=1 skip the clang differential even if clang is present
+
+set -u
+
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
+REPO_ROOT=$(cd "$SCRIPT_DIR/../.." && pwd)
+ECO_DIR="$SCRIPT_DIR"
+RECIPE_DIR="$SCRIPT_DIR/recipes"
+ECOSYS="$REPO_ROOT/scripts/ecosystem.sh"
+
+KIT="${KIT:-$REPO_ROOT/build/kit}"
+OPTS="${KIT_ECO_OPT:-O0 O1}"
+
+UPDATE=0
+SEL=""
+for a in "$@"; do
+ case "$a" in
+ --update) UPDATE=1 ;;
+ -*) echo "ecosystem: unknown flag $a" >&2; exit 2 ;;
+ *) SEL="$SEL $a" ;;
+ esac
+done
+
+if [ ! -x "$KIT" ]; then
+ echo "ecosystem: kit binary not found at $KIT (run: make bin)" >&2
+ exit 2
+fi
+
+# ---- host toolchain setup --------------------------------------------------
+# kit and clang both need the macOS SDK sysroot; on Linux they use the system
+# libc directly. LINK_LC pulls libc at the link step for kit.
+case "$(uname -s)" in
+ Darwin)
+ SDK="$(xcrun --sdk macosx --show-sdk-path 2>/dev/null || true)"
+ KIT_SYS="--sysroot $SDK"
+ CLANG_SYS="-isysroot $SDK"
+ ;;
+ *)
+ KIT_SYS=""
+ CLANG_SYS=""
+ ;;
+esac
+LINK_LC="-lc"
+
+HAVE_CLANG=0
+if [ "${KIT_ECO_NO_CLANG:-0}" != 1 ] && command -v clang >/dev/null 2>&1; then
+ HAVE_CLANG=1
+fi
+
+KIT_KIT_DIR="$REPO_ROOT/test/lib"
+# shellcheck disable=SC1090
+. "$REPO_ROOT/test/lib/kit_sh_kit.sh"
+kit_report_init
+
+WORK=$(mktemp -d "${TMPDIR:-/tmp}/kit-ecosystem.XXXXXX")
+trap 'rm -rf "$WORK"' EXIT
+
+# Failures are PRESERVED here (build/ is gitignored) so they can be dug into
+# later: each failing build leaves its captured stderr, the source list, and an
+# exact command log. Cleared at the start of every run.
+FAILDIR="$REPO_ROOT/build/ecosystem-fail"
+rm -rf "$FAILDIR"
+
+# preserve_fail NAME OPT TAG OUTDIR — copy a failing build's artifacts out of the
+# temp workdir into FAILDIR and print where they landed.
+preserve_fail() {
+ pf="$FAILDIR/$1-$2-$3"
+ mkdir -p "$pf"
+ cp "$4/build.err" "$pf/build.err" 2>/dev/null || true
+ cp "$4/srcs" "$pf/srcs" 2>/dev/null || true
+ cp "$4/cmds.sh" "$pf/cmds.sh" 2>/dev/null || true
+ cp "$4/out" "$pf/out" 2>/dev/null || true
+ cp "$4/run.err" "$pf/run.err" 2>/dev/null || true
+ printf ' -> preserved: %s\n' "$pf"
+}
+
+# ---- generic build: compile lib TUs -> static lib -> link driver -----------
+# build_with TAG OPT OUTDIR (TAG = kit|clang). Uses ECO_* + SRC from caller.
+# Returns 0 on a runnable $OUTDIR/app, non-zero (with $OUTDIR/build.err) on any
+# compile/ar/link failure.
+build_with() {
+ bw_tag=$1; bw_opt=$2; bw_od=$3
+ mkdir -p "$bw_od/obj"
+ : > "$bw_od/build.err"
+ : > "$bw_od/cmds.sh"
+
+ # Log each toolchain command (for the preserved repro) then run it.
+ log_run() { printf '%s\n' "$*" >> "$bw_od/cmds.sh"; "$@"; }
+
+ bw_inc=""
+ for i in $ECO_INCLUDES; do bw_inc="$bw_inc -I$SRC/$i"; done
+
+ # Library translation units (recipe-defined enumeration).
+ eco_lib_srcs > "$bw_od/srcs"
+ while IFS= read -r c; do
+ [ -z "$c" ] && continue
+ o="$bw_od/obj/$(printf '%s' "$c" | tr '/.' '__').o"
+ if [ "$bw_tag" = kit ]; then
+ log_run "$KIT" cc "-$bw_opt" $KIT_SYS $ECO_DEFINES $bw_inc -c "$c" -o "$o" 2>>"$bw_od/build.err" || return 1
+ else
+ log_run clang "-$bw_opt" $CLANG_SYS $ECO_DEFINES $bw_inc -c "$c" -o "$o" 2>>"$bw_od/build.err" || return 1
+ fi
+ done < "$bw_od/srcs"
+
+ # Archive.
+ if [ "$bw_tag" = kit ]; then
+ log_run "$KIT" ar rcs "$bw_od/lib.a" "$bw_od"/obj/*.o 2>>"$bw_od/build.err" || return 1
+ else
+ log_run ar rcs "$bw_od/lib.a" "$bw_od"/obj/*.o 2>>"$bw_od/build.err" || return 1
+ fi
+
+ # Driver main: "use/<f>.c" lives in test/ecosystem; anything else is the
+ # project's own main, relative to its source root.
+ case "$ECO_DRIVER" in
+ use/*) bw_drv="$ECO_DIR/$ECO_DRIVER" ;;
+ *) bw_drv="$SRC/$ECO_DRIVER" ;;
+ esac
+
+ if [ "$bw_tag" = kit ]; then
+ log_run "$KIT" cc "-$bw_opt" $KIT_SYS $ECO_DEFINES $bw_inc "$bw_drv" "$bw_od/lib.a" $ECO_LIBS $LINK_LC -o "$bw_od/app" 2>>"$bw_od/build.err" || return 1
+ else
+ log_run clang "-$bw_opt" $CLANG_SYS $ECO_DEFINES $bw_inc "$bw_drv" "$bw_od/lib.a" $ECO_LIBS -o "$bw_od/app" 2>>"$bw_od/build.err" || return 1
+ fi
+ return 0
+}
+
+# ---- per-project driver ----------------------------------------------------
+run_project() {
+ name=$1
+ recipe="$RECIPE_DIR/$name.recipe"
+ if [ ! -f "$recipe" ]; then kit_fail "$name" "no recipe"; return; fi
+
+ # Reset the recipe contract to defaults, then source.
+ ECO_VERSION=; ECO_URL=; ECO_SHA256=; ECO_SRCDIR=.
+ ECO_INCLUDES="."; ECO_DEFINES=""; ECO_LIBS=""; ECO_DRIVER=""; ECO_BUG=""
+ eco_lib_srcs() { :; }; eco_run() { :; }
+ # shellcheck disable=SC1090
+ . "$recipe"
+ export ECO_DIR ECO_SRC
+
+ SRC="$("$ECOSYS" srcdir "$name" 2>/dev/null || true)"
+ if [ -z "$SRC" ] || [ ! -d "$SRC" ]; then
+ kit_skip "$name" "not provisioned — run: make provision-ecosystem"
+ return
+ fi
+ ECO_SRC="$SRC"; export ECO_SRC
+
+ golden="$ECO_DIR/expected/$name.txt"
+
+ for opt in $OPTS; do
+ kod="$WORK/$name/kit-$opt"; mkdir -p "$kod"
+
+ # Failures are kept RED on purpose (no xfail) so known kit bugs stay visible
+ # until fixed; ECO_BUG only annotates the failure with a tracking pointer.
+ if ! build_with kit "$opt" "$kod"; then
+ [ -n "$ECO_BUG" ] && bug=" [known: $ECO_BUG]" || bug=""
+ kit_fail "$name:$opt:build" "kit build failed$bug"
+ tail -8 "$kod/build.err" | sed 's/^/ | /'
+ preserve_fail "$name" "$opt" build "$kod"
+ continue
+ fi
+
+ eco_run "$kod/app" > "$kod/out" 2>"$kod/run.err"; krc=$?
+ if [ "$krc" -ne 0 ]; then
+ [ -n "$ECO_BUG" ] && bug=" [known: $ECO_BUG]" || bug=""
+ kit_fail "$name:$opt:run" "exit=$krc$bug"
+ tail -6 "$kod/run.err" | sed 's/^/ | /'
+ preserve_fail "$name" "$opt" run "$kod"
+ continue
+ fi
+
+ # --update: (re)write the golden from clang (preferred) or kit output.
+ if [ "$UPDATE" = 1 ]; then
+ if [ "$HAVE_CLANG" = 1 ]; then
+ cod="$WORK/$name/clang-$opt"; mkdir -p "$cod"
+ if build_with clang "$opt" "$cod"; then
+ eco_run "$cod/app" > "$cod/out" 2>/dev/null && cp "$cod/out" "$golden"
+ else cp "$kod/out" "$golden"; fi
+ else cp "$kod/out" "$golden"; fi
+ kit_pass "$name:$opt:golden-updated"
+ continue
+ fi
+
+ # Golden assertion.
+ if [ -f "$golden" ]; then
+ if diff -u "$golden" "$kod/out" >/dev/null 2>&1; then
+ kit_pass "$name:$opt:golden"
+ else
+ kit_fail "$name:$opt:golden"
+ diff -u "$golden" "$kod/out" 2>&1 | head -20 | sed 's/^/ | /'
+ preserve_fail "$name" "$opt" golden "$kod"
+ cp "$golden" "$FAILDIR/$name-$opt-golden/golden.expected" 2>/dev/null || true
+ fi
+ else
+ kit_skip "$name:$opt:golden" "no golden (run --update)"
+ fi
+
+ # Differential vs clang.
+ if [ "$HAVE_CLANG" = 1 ]; then
+ cod="$WORK/$name/clang-$opt"; mkdir -p "$cod"
+ if build_with clang "$opt" "$cod"; then
+ eco_run "$cod/app" > "$cod/out" 2>/dev/null
+ if diff -u "$cod/out" "$kod/out" >/dev/null 2>&1; then
+ kit_pass "$name:$opt:vs-clang"
+ else
+ kit_fail "$name:$opt:vs-clang"
+ diff -u "$cod/out" "$kod/out" 2>&1 | head -20 | sed 's/^/ | /'
+ preserve_fail "$name" "$opt" vs-clang "$kod"
+ cp "$cod/out" "$FAILDIR/$name-$opt-vs-clang/clang.out" 2>/dev/null || true
+ fi
+ else
+ kit_skip "$name:$opt:vs-clang" "clang build failed"
+ fi
+ else
+ kit_skip_na "$name:$opt:vs-clang" "clang not available"
+ fi
+ done
+}
+
+# ---- drive ----------------------------------------------------------------
+PROJECTS="${SEL:-$("$ECOSYS" list)}"
+for p in $PROJECTS; do
+ run_project "$p"
+done
+
+kit_summary "ecosystem"
+kit_exit
diff --git a/test/ecosystem/scripts/hello.lua b/test/ecosystem/scripts/hello.lua
@@ -0,0 +1,33 @@
+-- Deterministic Lua script run by the interpreter built from the Lua sources.
+-- Exercises tables, string formatting, closures, coroutines, pcall, and math.
+
+-- string.format goes through the host snprintf; fixed values keep it stable.
+print(string.format("fmt %d %.3f %s", 42, 2.5, ("ab"):rep(3)))
+
+-- table build + sort + concat
+local t = {}
+for i = 1, 8 do t[i] = (i * 7) % 11 end
+table.sort(t)
+print("sorted: " .. table.concat(t, ","))
+
+-- closure / counter
+local function counter()
+ local n = 0
+ return function() n = n + 1; return n end
+end
+local c = counter()
+print("counter: " .. c() .. c() .. c())
+
+-- coroutine producing a fixed sequence
+local co = coroutine.wrap(function()
+ for i = 1, 4 do coroutine.yield(i * i) end
+end)
+print("coro: " .. co() .. "," .. co() .. "," .. co() .. "," .. co())
+
+-- protected call over an error
+local ok, err = pcall(function() error("boom") end)
+print("pcall: " .. tostring(ok) .. " " .. (err:match("boom") or "?"))
+
+-- integer / float math (Lua 5.4 has a real integer subtype)
+print(string.format("math %d %d %.4f", 7 // 2, 2 ^ 10, math.sqrt(2)))
+print("type: " .. math.type(3) .. "/" .. math.type(3.0))
diff --git a/test/ecosystem/scripts/sqlite.sql b/test/ecosystem/scripts/sqlite.sql
@@ -0,0 +1,38 @@
+-- Deterministic SQL exercising the SQLite shell built from the amalgamation.
+-- Output must not depend on RNG, time, or rowid order, so every result set is
+-- explicitly ordered and no random()/datetime() functions are used.
+.mode list
+.headers off
+
+CREATE TABLE emp(id INTEGER PRIMARY KEY, name TEXT, dept TEXT, salary INTEGER);
+INSERT INTO emp(name, dept, salary) VALUES
+ ('alice', 'eng', 120),
+ ('bob', 'eng', 100),
+ ('carol', 'ops', 90),
+ ('dave', 'ops', 110),
+ ('erin', 'sales', 80);
+
+-- Aggregate + group + order.
+SELECT dept, count(*), sum(salary), avg(salary)
+ FROM emp GROUP BY dept ORDER BY dept;
+
+-- Join a CTE against the base table.
+WITH raise(dept, pct) AS (VALUES('eng', 10), ('ops', 5))
+SELECT e.name, e.salary, e.salary + e.salary * r.pct / 100 AS adjusted
+ FROM emp e JOIN raise r ON e.dept = r.dept
+ ORDER BY e.name;
+
+-- Recursive CTE (sum of 1..10).
+WITH RECURSIVE seq(x) AS (
+ VALUES(1) UNION ALL SELECT x + 1 FROM seq WHERE x < 10
+) SELECT group_concat(x, ','), sum(x) FROM seq;
+
+-- Scalar + string + built-in JSON functions.
+SELECT upper('kit'), length('database'), printf('%05d', 42),
+ json_extract('{"a":[7,8,9]}', '$.a[2]');
+
+-- Window function: running total ordered by salary.
+SELECT name, salary,
+ sum(salary) OVER (ORDER BY salary, name
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running
+ FROM emp ORDER BY salary, name;
diff --git a/test/ecosystem/use/use_cjson.c b/test/ecosystem/use/use_cjson.c
@@ -0,0 +1,52 @@
+/* use_cjson.c — exercise cJSON parse / navigate / build / serialize.
+ * Output is deterministic (no pointers, no RNG, no hash iteration). */
+#include "cJSON.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int main(void) {
+ const char *src =
+ "{\"name\":\"kit\",\"ok\":true,\"nums\":[3,1,2],"
+ "\"meta\":{\"v\":42,\"tags\":[\"a\",\"b\"]}}";
+
+ cJSON *root = cJSON_Parse(src);
+ if (!root) {
+ printf("parse-fail\n");
+ return 1;
+ }
+
+ cJSON *name = cJSON_GetObjectItemCaseSensitive(root, "name");
+ cJSON *ok = cJSON_GetObjectItemCaseSensitive(root, "ok");
+ cJSON *nums = cJSON_GetObjectItemCaseSensitive(root, "nums");
+ cJSON *meta = cJSON_GetObjectItemCaseSensitive(root, "meta");
+ cJSON *v = cJSON_GetObjectItemCaseSensitive(meta, "v");
+
+ printf("name=%s ok=%d v=%d nlen=%d\n", cJSON_GetStringValue(name),
+ cJSON_IsTrue(ok), (int)cJSON_GetNumberValue(v),
+ cJSON_GetArraySize(nums));
+
+ int sum = 0;
+ cJSON *it = NULL;
+ cJSON_ArrayForEach(it, nums) sum += (int)cJSON_GetNumberValue(it);
+ printf("nums-sum=%d\n", sum);
+
+ /* Build a fresh document and round-trip it. */
+ cJSON *out = cJSON_CreateObject();
+ cJSON_AddStringToObject(out, "lib", "cJSON");
+ cJSON_AddNumberToObject(out, "answer", 42);
+ cJSON *arr = cJSON_AddArrayToObject(out, "seq");
+ for (int i = 1; i <= 4; i++) cJSON_AddItemToArray(arr, cJSON_CreateNumber(i * i));
+ char *printed = cJSON_PrintUnformatted(out);
+ printf("built=%s\n", printed);
+
+ cJSON *re = cJSON_Parse(printed);
+ printf("reparse-answer=%d\n",
+ (int)cJSON_GetNumberValue(cJSON_GetObjectItem(re, "answer")));
+
+ free(printed);
+ cJSON_Delete(re);
+ cJSON_Delete(out);
+ cJSON_Delete(root);
+ return 0;
+}
diff --git a/test/ecosystem/use/use_lz4.c b/test/ecosystem/use/use_lz4.c
@@ -0,0 +1,32 @@
+/* use_lz4.c — exercise LZ4 block compress / decompress round-trips.
+ * Deterministic: fixed inputs, reports sizes and exact-match flags. */
+#include "lz4.h"
+#include <stdio.h>
+#include <string.h>
+
+static int roundtrip(const char *label, const char *in, int n) {
+ char comp[4096];
+ char dec[4096];
+ int cl = LZ4_compress_default(in, comp, n, (int)sizeof comp);
+ if (cl <= 0) {
+ printf("%s compress-fail\n", label);
+ return 0;
+ }
+ int dl = LZ4_decompress_safe(comp, dec, cl, (int)sizeof dec);
+ int ok = (dl == n) && (memcmp(in, dec, (size_t)n) == 0);
+ printf("%s in=%d comp=%d out=%d match=%d\n", label, n, cl, dl, ok);
+ return ok;
+}
+
+int main(void) {
+ const char *a = "the quick brown fox jumps over the lazy dog";
+ /* Highly compressible repeated pattern. */
+ char b[1024];
+ for (int i = 0; i < (int)sizeof b; i++) b[i] = (char)('A' + (i % 8));
+
+ roundtrip("text", a, (int)strlen(a));
+ roundtrip("repeat", b, (int)sizeof b);
+
+ printf("versionstr=%s\n", LZ4_versionString());
+ return 0;
+}
diff --git a/test/ecosystem/use/use_miniz.c b/test/ecosystem/use/use_miniz.c
@@ -0,0 +1,37 @@
+/* use_miniz.c — exercise miniz deflate/inflate round-trips + CRC-32.
+ * Deterministic output. */
+#include "miniz.h"
+#include <stdio.h>
+#include <string.h>
+
+int main(void) {
+ const char *msg =
+ "miniz round-trip: hello hello hello world world 12341234 abcabcabc";
+ size_t n = strlen(msg);
+
+ unsigned char comp[512];
+ unsigned char dec[512];
+ mz_ulong cl = sizeof comp;
+ mz_ulong dl = sizeof dec;
+
+ int rc = mz_compress(comp, &cl, (const unsigned char *)msg, n);
+ if (rc != MZ_OK) {
+ printf("compress-fail %d\n", rc);
+ return 1;
+ }
+ rc = mz_uncompress(dec, &dl, comp, cl);
+ if (rc != MZ_OK) {
+ printf("uncompress-fail %d\n", rc);
+ return 1;
+ }
+
+ int match = (dl == n) && (memcmp(msg, dec, n) == 0);
+ printf("in=%lu comp=%lu out=%lu match=%d\n", (unsigned long)n,
+ (unsigned long)cl, (unsigned long)dl, match);
+
+ mz_ulong crc = mz_crc32(MZ_CRC32_INIT, (const unsigned char *)msg, n);
+ printf("crc32=%08lx\n", (unsigned long)crc);
+ printf("adler-of-deflate=%lu\n",
+ (unsigned long)mz_adler32(MZ_ADLER32_INIT, comp, cl));
+ return 0;
+}
diff --git a/test/ecosystem/use/use_tinyexpr.c b/test/ecosystem/use/use_tinyexpr.c
@@ -0,0 +1,35 @@
+/* use_tinyexpr.c — exercise tinyexpr expression compilation + variable binding.
+ * Deterministic output (fixed expressions, %.6g formatting). */
+#include "tinyexpr.h"
+#include <stdio.h>
+
+int main(void) {
+ /* Pure constant-folding interpretations. */
+ const char *exprs[] = {
+ "sqrt(3^2 + 4^2)",
+ "2 * (3 + 4) - 5",
+ "sin(0) + cos(0)",
+ "max(2, min(9, 7))",
+ };
+ int err;
+ for (int i = 0; i < 4; i++) {
+ double r = te_interp(exprs[i], &err);
+ printf("expr[%d]=%.6g err=%d\n", i, r, err);
+ }
+
+ /* Compile-once, evaluate-many with bound variables. */
+ double x = 0, y = 0;
+ te_variable vars[] = {{"x", &x, 0, 0}, {"y", &y, 0, 0}};
+ te_expr *e = te_compile("x*x + y*y", vars, 2, &err);
+ if (!e) {
+ printf("compile-fail at %d\n", err);
+ return 1;
+ }
+ for (int i = 1; i <= 3; i++) {
+ x = i;
+ y = i + 1;
+ printf("f(%d,%d)=%.6g\n", i, i + 1, te_eval(e));
+ }
+ te_free(e);
+ return 0;
+}
diff --git a/test/ecosystem/use/use_yyjson.c b/test/ecosystem/use/use_yyjson.c
@@ -0,0 +1,43 @@
+/* use_yyjson.c — exercise yyjson read / navigate / mutate / write.
+ * Deterministic output. (Gate is KNOWN-RED: kit miscompiles yyjson.c today.) */
+#include "yyjson.h"
+#include <stdio.h>
+#include <string.h>
+
+int main(void) {
+ const char *json =
+ "{\"name\":\"kit\",\"n\":42,\"arr\":[10,20,30],\"nested\":{\"ok\":true}}";
+
+ yyjson_doc *doc = yyjson_read(json, strlen(json), 0);
+ if (!doc) {
+ printf("read-fail\n");
+ return 1;
+ }
+ yyjson_val *root = yyjson_doc_get_root(doc);
+ printf("name=%s n=%lld arrlen=%zu\n",
+ yyjson_get_str(yyjson_obj_get(root, "name")),
+ (long long)yyjson_get_sint(yyjson_obj_get(root, "n")),
+ yyjson_arr_size(yyjson_obj_get(root, "arr")));
+
+ long long sum = 0;
+ yyjson_val *arr = yyjson_obj_get(root, "arr");
+ size_t idx, max;
+ yyjson_val *v;
+ yyjson_arr_foreach(arr, idx, max, v) sum += yyjson_get_sint(v);
+ printf("arr-sum=%lld nested-ok=%d\n", sum,
+ yyjson_get_bool(yyjson_obj_get(yyjson_obj_get(root, "nested"), "ok")));
+
+ /* Build a mutable doc and serialize it. */
+ yyjson_mut_doc *mdoc = yyjson_mut_doc_new(NULL);
+ yyjson_mut_val *obj = yyjson_mut_obj(mdoc);
+ yyjson_mut_doc_set_root(mdoc, obj);
+ yyjson_mut_obj_add_str(mdoc, obj, "lib", "yyjson");
+ yyjson_mut_obj_add_int(mdoc, obj, "answer", 42);
+ char *out = yyjson_mut_write(mdoc, 0, NULL);
+ printf("built=%s\n", out ? out : "(null)");
+
+ free(out);
+ yyjson_mut_doc_free(mdoc);
+ yyjson_doc_free(doc);
+ return 0;
+}