kit

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

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:
Mdoc/plan/RELEASE.md | 38++++++++++++++++++++++++++------------
Mmk/test.mk | 17+++++++++++++++++
Ascripts/ecosystem.sh | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/ecosystem/README.md | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/ecosystem/expected/cjson.txt | 4++++
Atest/ecosystem/expected/lua.txt | 7+++++++
Atest/ecosystem/expected/lz4.txt | 3+++
Atest/ecosystem/expected/miniz.txt | 3+++
Atest/ecosystem/expected/sqlite.txt | 14++++++++++++++
Atest/ecosystem/expected/tinyexpr.txt | 7+++++++
Atest/ecosystem/known_bugs/README.md | 28++++++++++++++++++++++++++++
Atest/ecosystem/known_bugs/sqlite-o1-lkit-ro.md | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/ecosystem/known_bugs/yyjson-write-label.md | 43+++++++++++++++++++++++++++++++++++++++++++
Atest/ecosystem/recipes/cjson.recipe | 15+++++++++++++++
Atest/ecosystem/recipes/lua.recipe | 25+++++++++++++++++++++++++
Atest/ecosystem/recipes/lz4.recipe | 15+++++++++++++++
Atest/ecosystem/recipes/miniz.recipe | 18++++++++++++++++++
Atest/ecosystem/recipes/sqlite.recipe | 46++++++++++++++++++++++++++++++++++++++++++++++
Atest/ecosystem/recipes/tinyexpr.recipe | 17+++++++++++++++++
Atest/ecosystem/recipes/yyjson.recipe | 23+++++++++++++++++++++++
Atest/ecosystem/run.sh | 252+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/ecosystem/scripts/hello.lua | 33+++++++++++++++++++++++++++++++++
Atest/ecosystem/scripts/sqlite.sql | 38++++++++++++++++++++++++++++++++++++++
Atest/ecosystem/use/use_cjson.c | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/ecosystem/use/use_lz4.c | 32++++++++++++++++++++++++++++++++
Atest/ecosystem/use/use_miniz.c | 37+++++++++++++++++++++++++++++++++++++
Atest/ecosystem/use/use_tinyexpr.c | 35+++++++++++++++++++++++++++++++++++
Atest/ecosystem/use/use_yyjson.c | 43+++++++++++++++++++++++++++++++++++++++++++
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; +}