docs: track fork-over-cisco divergence as a patch series
ober
d1ad34b6b9bf39159d9ee94d939a8d6a2d057be4
new file mode 100644 --- /dev/null +++ b/docs/chez-fork.md @@ -0,0 +1,121 @@ +# ChezScheme Fork Notes + +The Chez Scheme bundled at `vendor/ChezScheme/` is **not stock**. It's a +soft fork hosted at `git.sr.ht/~lisp/ChezScheme` (working tree: +`~/mine/ChezScheme`), branched from upstream `github.com/cisco/ChezScheme` +and carrying jerboa-relevant patches. + +This doc is the **narrative** layer: themes, motivations, what +jerboa depends on. The **line-level** layer — one unified-diff file +per fork-side commit, carrying its commit message — lives at +[`../vendor/ChezScheme-patches/`](../vendor/ChezScheme-patches/). +Read this doc for orientation; drill into the patches for specifics. +Regenerate the patch series with `./tools/regen-chez-patches.sh` +when `~/mine/ChezScheme` advances. + +## At a glance + +- **Upstream:** `github.com/cisco/ChezScheme` +- **Fork:** `git.sr.ht/~lisp/ChezScheme` +- **Last common commit at time of writing:** `4bde3b35` +- **Divergence:** ~52 commits, ~5500 insertions / 243 deletions across 63 files +- **Vendored snapshot in this repo:** `vendor/ChezScheme/` @ `a4be658a` + (see `vendor/ChezScheme/UPSTREAM.md`) + +To re-derive the divergence yourself: + +``` +cd ~/mine/ChezScheme +git fetch upstream +git log --no-merges --oneline upstream/main..HEAD +git diff --stat upstream/main..HEAD +``` + +## Themes + +The divergence groups into five themes. SHAs below are fork-local — +resolve them in `~/mine/ChezScheme`, not in the vendored tree (where +history was stripped). + +### 1. Security hardening + +Why: jerboa ships as a single statically-linked binary that parses +untrusted FASL/data; stock Chez defaults assume a trusted build env. + +- `c3c3d6f3` — `--static` and `--foreign-libs` configure options + (required by the musl-static pipeline; enables hermetic binaries) +- `b3f35405` — harden FASL deserialization, FFI, and build scripts + against malformed input +- `aaa82190` — vfasl bounds checks + `path_append` hardening + HPUX typo +- `829bc806` — ENDBR64 emission at function entries (Intel CET/IBT) +- `beefa2a6` — `--enable-harden` configure flag + ARM64 BTI landing pads + +Touches: `c/fasl.c`, `c/vfasl.c`, `c/main.c`, `s/x86_64.ss`, +`s/arm64.ss`, `configure`, `c/build.zuo`. + +### 2. Optimizer enhancements ("Phase N" work) + +Why: jerboa's stdlib (persistent collections, hashtables, defstruct, +match) leans hard on Chez's optimizer. Each "Phase N" is a bottleneck we +identified and fixed in Chez rather than working around in jerboa. + +- `506f43ac`, `b3ea8e63` — newhash: sealed-record dispatch for generic + + bulk hashtable ops +- `2629173b`, `5c25af3e`, `e37d7c58`, `03087783` — cptypes hashtable + specialization (cell/ref-cell, clear!/copy, 2-arg eq-hashtable) +- `1776fb2f` — cp0 literal string-append folding + +Touches: `s/cp0.ss`, `s/cptypes.ss`, `s/cptypes-lattice.ss`, +`s/newhash.ss`, `s/primdata.ss`. + +Regression guards: `mats/cptypes.ms`, `mats/hash.ms`, `mats/record.ms`, +`mats/bytevector.ms`. + +The deeper backstory for each Phase lives in `vendor/ChezScheme/plan.md` +(2500+ lines of running log — kept inside the fork, see §5). + +### 3. New primitives + +Why: jerboa's stdlib calls these from Scheme without round-tripping +through C FFI. Faster, and lets the optimizer see through them. + +- `9c919405` — `bytevector-slice`, `bytevector-append` +- `6a7a21ce` — `base64-encode`, `base64-decode` +- `ada7cd0c` — `sha1-bytevector`, `sha256-bytevector` +- `b621c8d6` — ordered-hashtable (Phase 68) + record-walk (Phase 72) + +Touches: `s/bytevector.ss`, `s/newhash.ss`, `s/primdata.ss`. + +### 4. Build & packaging + +- `457a455c` — `sync-upstream` + `sync-upstream-status` make targets + (maintenance helpers for this fork) +- `tools/gen-static-ffi.sh` (new) — generates static FFI tables for + `--static` builds +- `make-android` (new) + `d293dea3` — Android/Termux build wiring +- `5f9c697c` — `install.zuo`: copies instead of hard links +- `wininstall/*.wxs` — WiX installer updates for `--static` + +### 5. Jerboa-side docs living inside the fork + +Three files live in `vendor/ChezScheme/` that are *jerboa* documentation, +not Chez source. They ride along in the fork for editing convenience but +are **not upstream-relevant** — never include them in a patch sent to +cisco/ChezScheme. + +- `plan.md` — running log of Phase N investigations and outcomes +- `findings.md` — research notes on optimizer behavior +- `CLAUDE.md` — Claude instructions for work *inside* the Chez tree + (separate from jerboa-root `CLAUDE.md`) +- `bench/jerboa-bench.ss` — jerboa-specific benchmark suite + +### 6. Routine fixes not yet upstreamed + +- `a4be658a` — ppc32 + logtest repairs (#1045) + +## Keeping this doc current + +When the fork advances or the vendored snapshot is resynced (recipe in +`vendor/ChezScheme/UPSTREAM.md`), re-run the at-a-glance commands above +and slot new commits into the themes. Add a new theme only if the work +genuinely doesn't fit (new platform port, new subsystem, etc.). new file mode 100755 --- /dev/null +++ b/tools/regen-chez-patches.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Regenerate vendor/ChezScheme-patches/ from ~/mine/ChezScheme. +# +# Captures each fork-over-cisco commit as a numbered .patch file, +# skipping commits that only touched jerboa-side docs. +# +# See docs/chez-fork.md for the narrative; see +# vendor/ChezScheme-patches/README.md for how to read the output. +# +# Overrides: +# FORK_DIR=<path> (default: $HOME/mine/ChezScheme) +# UPSTREAM_REF=<ref> (default: upstream/main) + +set -euo pipefail + +FORK_DIR="${FORK_DIR:-$HOME/mine/ChezScheme}" +UPSTREAM_REF="${UPSTREAM_REF:-upstream/main}" +OUT_DIR="$(cd "$(dirname "$0")/.." && pwd)/vendor/ChezScheme-patches" + +if [ ! -d "$FORK_DIR/.git" ]; then + echo "ERROR: $FORK_DIR is not a git repo" >&2 + echo "Set FORK_DIR=<path> or clone the fork to ~/mine/ChezScheme" >&2 + exit 1 +fi + +# These paths are excluded from both commit selection (commits that +# only touch them are skipped) and from each patch's diff body. +SKIP=( + ':!plan.md' + ':!findings.md' + ':!CLAUDE.md' + ':!bench/jerboa-bench.ss' +) + +(cd "$FORK_DIR" && git fetch upstream --quiet) + +mkdir -p "$OUT_DIR" +# Wipe old patches but preserve README.md and any other non-patch files. +find "$OUT_DIR" -maxdepth 1 -name '*.patch' -delete + +( + cd "$FORK_DIR" + i=1 + for sha in $(git log --no-merges --reverse --format=%H \ + "${UPSTREAM_REF}..HEAD" -- "${SKIP[@]}"); do + git format-patch -1 "$sha" \ + --start-number "$i" \ + -o "$OUT_DIR" \ + -- "${SKIP[@]}" > /dev/null + i=$((i + 1)) + done +) + +count=$(find "$OUT_DIR" -maxdepth 1 -name '*.patch' | wc -l | tr -d ' ') +echo "wrote $count patches to $OUT_DIR" new file mode 100644 --- /dev/null +++ b/vendor/ChezScheme-patches/0001-add-static-and-foreign-libs-configure-options-for-st.patch @@ -0,0 +1,537 @@ +From c3c3d6f32913c32a37a83f965d597a91efae1215 Mon Sep 17 00:00:00 2001 +From: Jaime Fournier <jaimef@linbsd.org> +Date: Tue, 3 Mar 2026 13:23:59 -0700 +Subject: [PATCH] add --static and --foreign-libs configure options for static + binary builds + +Support fully static Chez Scheme binaries with embedded boot files and +statically linked FFI libraries: + +- configure: add --static (implies --disable-x11/curses/iconv, uses + -static LDFLAGS, removes -ldl) and --foreign-libs=path1.a:path2.a:... + for registering FFI symbols from static libraries +- c/version.h: guard all LOAD_SHARED_OBJECT definitions behind + DISABLE_DYNAMIC_LOAD so dlopen/dlsym are excluded from static builds +- c/build.zuo: generate static_ffi.c (FFI symbol registration via + Sforeign_symbol) and static_boot.c (embedded boot files via xxd -i + and Sregister_boot_file_bytes) when static build is configured +- c/main.c: support STATIC_BOOT for embedded boot file registration + before Sbuild_heap, skip when explicit -b/--boot flags are given + (preserving bootstrap builds); change CUSTOM_INIT to use extern + forward declaration when defined +- tools/gen-static-ffi.sh: new script to extract exported text symbols + from .a files and generate Sforeign_symbol registration code + +Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> +--- + c/build.zuo | 101 ++++++++++++++++++++++++++++++++++++++-- + c/main.c | 31 +++++++++++- + c/version.h | 18 ++++++- + configure | 66 ++++++++++++++++++++------ + tools/gen-static-ffi.sh | 61 ++++++++++++++++++++++++ + 5 files changed, 255 insertions(+), 22 deletions(-) + create mode 100755 tools/gen-static-ffi.sh + +diff --git a/c/build.zuo b/c/build.zuo +index dde5b3b6..396adcff 100644 +--- a/c/build.zuo ++++ b/c/build.zuo +@@ -73,8 +73,8 @@ + (define c-config-file + (at-dir "config.h")) + (define c-config-keys +- '(disablecurses disablex11 enablelibffi alwaysUseBootFile preloadBootFiles relativeBootFiles +- InstallBin InstallLib)) ++ '(disablecurses disablex11 disabledynload enablelibffi alwaysUseBootFile preloadBootFiles ++ relativeBootFiles foreignLibs InstallBin InstallLib)) + + ;; sources for "kernel.o": + (define kernel-src-files +@@ -178,6 +178,25 @@ + + (define preload-files (get-preload-files config at-dir mboot)) + ++ ;; Static build support ++ (define static-build? (equal? (hash-ref config 'disabledynload #f) "yes")) ++ (define foreign-libs-str (or (hash-ref config 'foreignLibs #f) "")) ++ (define foreign-libs (if (equal? foreign-libs-str "") ++ '() ++ (string-split foreign-libs-str ":"))) ++ (define static-ffi.c (at-dir "static_ffi.c")) ++ (define static-ffi.o (at-dir (.c->.o "static_ffi"))) ++ (define static-boot.c (at-dir "static_boot.c")) ++ (define static-boot.o (at-dir (.c->.o "static_boot"))) ++ (define static-objs (append (if (pair? foreign-libs) (list static-ffi.o) '()) ++ (if static-build? (list static-boot.o) '()))) ++ (define static-lib-inputs (if (pair? foreign-libs) ++ (map (lambda (p) (if (relative-path? p) ++ (at-dir "../.." p) ++ p)) ++ foreign-libs) ++ '())) ++ + (define (compile-one .c .o) + (c-compile .o .c + (let* ([config (config-define config cpu)] +@@ -234,16 +253,18 @@ + ,void + :recur] + +- [:target ,exe (,kernel-dep ,@main-objs ,@res-deps ,@preload-files ,(at-source "mingw.zuo")) ++ [:target ,exe (,kernel-dep ,@main-objs ,@static-objs ,@res-deps ,@preload-files ,(at-source "mingw.zuo")) + ,(lambda (path token) + (mkdir-p (path-only path)) + (c-link path + (append main-objs ++ static-objs + res-deps + (append + (list kernel-dep) + (if zlib-system-lib '() (list zlib-lib)) + (if lz4-system-lib '() (list lz4-lib))) ++ static-lib-inputs + (if (and (not msvc?) + (for-windows? config)) + (make-mingw-to-ms-link-libraries m (at-dir) config) +@@ -395,7 +416,27 @@ + ,@(map (lambda (.c .o) + `[:target ,.o (,.c ,@main-hdrs) + ,(lambda (.o target) +- (compile-one .c .o))]) ++ (define extra-cppflags ++ (~a (if static-build? " -DSTATIC_BOOT=static_boot_init" "") ++ (if (pair? foreign-libs) " -DCUSTOM_INIT=static_ffi_init" ""))) ++ (c-compile .o .c ++ (let* ([config (config-define config cpu)] ++ [config (config-merge config 'CPPFLAGS ++ (~a (or (lookup 'mdinclude) "") ++ extra-cppflags))] ++ [config (config-merge config 'CFLAGS (or (lookup 'warningFlags) ""))] ++ [config (config-include config ++ (at-dir (build-path "../boot" m)) ++ (at-dir ".") ++ (at-source "."))] ++ [config (if zlib-system-lib ++ config ++ (config-include config zlib-dir))] ++ [config (if (equal? (or (lookup 'LZ4Lib) "") "") ++ (config-include config ++ (build-path lz4-dir "lib")) ++ config)]) ++ config)))]) + main-srcs main-objs) + + [:depend ,(at-dir (.c->.o "scheme.c")) (,(at-source "itest.c"))] +@@ -426,6 +467,8 @@ + "#define DISABLE_CURSES")) + (line (and (equal? (hash-ref config 'disablex11 #f) "yes") + "#define DISABLE_X11")) ++ (line (and (equal? (hash-ref config 'disabledynload #f) "yes") ++ "#define DISABLE_DYNAMIC_LOAD")) + (line (and (equal? (hash-ref config 'enablelibffi #f) "yes") + "#define ENABLE_LIBFFI")) + (line (and (not (for-windows? config)) +@@ -484,12 +527,60 @@ + ;; entry in ;; .gitattributes for build.zuo. + (display-to-file "$Format:%H$\ngit\n" (at-mach "revision") :truncate))])))] + ++ ;; Static FFI: generate and compile registration code for statically linked libraries ++ ,@(if (pair? foreign-libs) ++ `([:target ,static-ffi.c (,(at-source "../tools/gen-static-ffi.sh")) ++ ,(lambda (path token) ++ (define p (shell (apply build-shell ++ (cons (string->shell (at-source "../tools/gen-static-ffi.sh")) ++ (map string->shell static-lib-inputs))) ++ (hash 'stdout 'pipe))) ++ (define out (fd-read (hash-ref p 'stdout) eof)) ++ (process-wait (hash-ref p 'process)) ++ (fd-close (hash-ref p 'stdout)) ++ (display-to-file out path :truncate))] ++ [:target ,static-ffi.o (,static-ffi.c ,@kernel-hdrs) ++ ,(lambda (.o token) ++ (compile-one static-ffi.c .o))]) ++ '()) ++ ++ ;; Static boot: embed boot files as C byte arrays using xxd -i ++ ,@(if static-build? ++ (let ([petite-boot (at-dir (build-path "../boot" m "petite.boot"))] ++ [scheme-boot (at-dir (build-path "../boot" m "scheme.boot"))]) ++ `([:target ,static-boot.c (,petite-boot ,scheme-boot) ++ ,(lambda (path token) ++ (define (xxd-array file-path var-name) ++ (define p (shell (~a "xxd -i < " (string->shell file-path)) ++ (hash 'stdout 'pipe))) ++ (define out (fd-read (hash-ref p 'stdout) eof)) ++ (process-wait (hash-ref p 'process)) ++ (fd-close (hash-ref p 'stdout)) ++ (~a "static const unsigned char " var-name "[] = {\n" ++ out "};\n\n")) ++ (define s ++ (~a "/* Generated by build.zuo -- do not edit */\n" ++ "#include \"scheme.h\"\n\n" ++ (xxd-array petite-boot "petite_boot_data") ++ (xxd-array scheme-boot "scheme_boot_data") ++ "void static_boot_init(void) {\n" ++ " Sregister_boot_file_bytes(\"petite.boot\",\n" ++ " (void *)petite_boot_data, sizeof(petite_boot_data));\n" ++ " Sregister_boot_file_bytes(\"scheme.boot\",\n" ++ " (void *)scheme_boot_data, sizeof(scheme_boot_data));\n" ++ "}\n")) ++ (display-to-file s path :truncate))] ++ [:target ,static-boot.o (,static-boot.c ,@kernel-hdrs) ++ ,(lambda (.o token) ++ (compile-one static-boot.c .o))])) ++ '()) ++ + ;; keep "_zuo.db" and "_zuo_tc.db" here instead of in "boot" and "bin" directories + [:db-dir ,(at-dir ".")] + + [:target clean () + ,(lambda (token) +- (map rm* kernel-objs))] ++ (map rm* (append kernel-objs static-objs)))] + + #;(end-make-targets)))) + +diff --git a/c/main.c b/c/main.c +index bb46eefc..2f0c3a86 100644 +--- a/c/main.c ++++ b/c/main.c +@@ -24,10 +24,22 @@ + CUSTOM_INIT may be defined as a function with the signature shown to + perform boot-time initialization, e.g., registering foreign symbols. + ****/ +-#ifndef CUSTOM_INIT ++#ifdef CUSTOM_INIT ++extern void CUSTOM_INIT(void); ++#else + #define CUSTOM_INIT ((void (*)(void))0) + #endif /* CUSTOM_INIT */ + ++/**** ++ STATIC_BOOT may be defined as a function that registers embedded boot ++ files via Sregister_boot_file_bytes() before Sbuild_heap() is called. ++ This is used for fully static builds where boot files are compiled ++ into the executable. ++****/ ++#ifdef STATIC_BOOT ++extern void STATIC_BOOT(void); ++#endif ++ + /**** + ABNORMAL_EXIT may be defined as a function with the signature shown to + take some action, such as printing a special error message or performing +@@ -79,6 +91,9 @@ int wmain(int argc, wchar_t* wargv[], wchar_t* wenvp[]) { + int main(int argc, const char *argv[]) { + #endif /* WIN32 */ + int n, new_argc = 1; ++#ifdef STATIC_BOOT ++ int boot_given = 0; ++#endif + #ifdef SAVEDHEAPS + int compact = 1, savefile_level = 0; + const char *savefile = (char *)0; +@@ -131,12 +146,18 @@ int main(int argc, const char *argv[]) { + exit(1); + } + Sregister_boot_executable_relative_file(execpath, argv[n]); ++#ifdef STATIC_BOOT ++ boot_given = 1; ++#endif + } else if (strcmp(arg,"-B") == 0 || strcmp(arg,"--Boot") == 0) { + if (++n == argc) { + (void) fprintf(stderr,"%s requires argument\n", arg); + exit(1); + } + Sregister_boot_relative_file(argv[n]); ++#ifdef STATIC_BOOT ++ boot_given = 1; ++#endif + } else if (strcmp(arg,"--eedisable") == 0) { + #ifdef FEATURE_EXPEDITOR + expeditor_enable = 0; +@@ -291,6 +312,14 @@ int main(int argc, const char *argv[]) { + } + } + ++ /* Register embedded boot files for static builds when no ++ * explicit boot files were given on the command line. */ ++#ifdef STATIC_BOOT ++ if (!boot_given) { ++ STATIC_BOOT(); ++ } ++#endif ++ + /* must call Sbuild_heap after registering boot and heap files. + * Sbuild_heap() completes the initialization of the Scheme system + * and loads the boot or heap files. If no boot or heap files have +diff --git a/c/version.h b/c/version.h +index 08a3b587..cd41231d 100644 +--- a/c/version.h ++++ b/c/version.h +@@ -103,7 +103,9 @@ FORCEINLINE void store_unaligned_uptr(uptr *addr, uptr val) { + /* cosmo dylib support is experimental, disable when using cosmo libc + https://github.com/jart/cosmopolitan/blob/3.3.10/libc/dlopen/dlopen.c#L801 */ + #ifndef __COSMOPOLITAN__ +-# define LOAD_SHARED_OBJECT ++# ifndef DISABLE_DYNAMIC_LOAD ++# define LOAD_SHARED_OBJECT ++# endif + #endif + #define USE_MMAP + #define MMAP_HEAP +@@ -173,7 +175,9 @@ typedef int tputsputcchar; + + #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) + #define NOBLOCK O_NONBLOCK ++#ifndef DISABLE_DYNAMIC_LOAD + #define LOAD_SHARED_OBJECT ++#endif + #define USE_MMAP + #define MMAP_HEAP + #define IEEE_DOUBLE +@@ -204,7 +208,9 @@ typedef int tputsputcchar; + # define NETBSD + #endif + #define NOBLOCK O_NONBLOCK ++#ifndef DISABLE_DYNAMIC_LOAD + #define LOAD_SHARED_OBJECT ++#endif + #define USE_MMAP + #define MMAP_HEAP + #define IEEE_DOUBLE +@@ -236,7 +242,9 @@ typedef int tputsputcchar; + #define GETPAGESIZE() S_getpagesize() + #define GETWD(x) GETCWD(x, _MAX_PATH) + #define IEEE_DOUBLE ++#ifndef DISABLE_DYNAMIC_LOAD + #define LOAD_SHARED_OBJECT ++#endif + #define USE_VIRTUAL_ALLOC + #define NAN_INCLUDE <math.h> + #define MAKE_NAN(x) { x = sqrt(-1.0); } +@@ -306,7 +314,9 @@ struct timespec; + + #if defined(__OpenBSD__) && !defined(__Bitrig__) + #define NOBLOCK O_NONBLOCK ++#ifndef DISABLE_DYNAMIC_LOAD + #define LOAD_SHARED_OBJECT ++#endif + #define USE_MMAP + #define MMAP_HEAP + #define IEEE_DOUBLE +@@ -336,7 +346,9 @@ typedef int tputsputcchar; + #if defined(__APPLE__) + #define MACOSX + #define NOBLOCK O_NONBLOCK ++#ifndef DISABLE_DYNAMIC_LOAD + #define LOAD_SHARED_OBJECT ++#endif + #define USE_MMAP + #define MMAP_HEAP + #define IEEE_DOUBLE +@@ -386,7 +398,9 @@ typedef int tputsputcchar; + + #if defined(__QNX__) + #define NOBLOCK O_NONBLOCK ++#ifndef DISABLE_DYNAMIC_LOAD + #define LOAD_SHARED_OBJECT ++#endif + #define USE_MMAP + #define MMAP_HEAP + #define IEEE_DOUBLE +@@ -414,7 +428,9 @@ typedef int tputsputcchar; + + #if defined(sun) + #define NOBLOCK O_NONBLOCK ++#ifndef DISABLE_DYNAMIC_LOAD + #define LOAD_SHARED_OBJECT ++#endif + #define USE_MMAP + #define MMAP_HEAP + #define IEEE_DOUBLE +diff --git a/configure b/configure +index be2f9438..5669a4e2 100755 +--- a/configure ++++ b/configure +@@ -86,6 +86,9 @@ installpetitename="petite" + installscriptname="scheme-script" + unamebits="" + relativeBootFiles=yes ++staticbuild=no ++disabledynload=no ++foreignLibs="" + disablex11=no + disablecurses=no + disableiconv=no +@@ -402,6 +405,23 @@ while [ $# != 0 ] ; do + --disable-iconv) + disableiconv=yes + ;; ++ --static) ++ staticbuild=yes ++ disabledynload=yes ++ disablex11=yes ++ disablecurses=yes ++ disableiconv=yes ++ ;; ++ --foreign-libs=*) ++ foreignLibs=`echo $1 | sed -e 's/^--foreign-libs=//'` ++ if [ "$staticbuild" = "no" ]; then ++ staticbuild=yes ++ disabledynload=yes ++ disablex11=yes ++ disablecurses=yes ++ disableiconv=yes ++ fi ++ ;; + --enable-libffi) + libffi=yes + ;; +@@ -684,6 +704,8 @@ if [ "$help" = "yes" ]; then + echo " --disable-x11 disable X11 support" + echo " --disable-curses disable [n]curses support" + echo " --disable-iconv disable iconv support" ++ echo " --static build fully static binary" ++ echo " --foreign-libs=<path:...> statically link .a libs for FFI" + echo " --enable-libffi enable libffi support for pb" + echo " --disable-auto-flags no auto additions to CFLAGS/LDFLAGS/LIBS" + echo " --enable-warning-flags add GCC warning flags to CFLAGS" +@@ -891,23 +913,31 @@ fi + + # Add automatic linking flags, unless suppressed by --disable-auto-flags + if [ "$addflags" = "yes" ] ; then +- case "${flagsm}" in +- *le|*gnu|*hk) +- LDFLAGS="${LDFLAGS} -rdynamic" +- ;; +- *fb|*nb) +- LDFLAGS="${LDFLAGS} -rdynamic -L/usr/local/lib" +- ;; +- *ob) +- LDFLAGS="${LDFLAGS} -rdynamic -Wl,--export-dynamic -Wl,-zwxneeded -L/usr/local/lib" +- ;; +- *) +- ;; +- esac ++ if [ "$staticbuild" = "yes" ] ; then ++ LDFLAGS="${LDFLAGS} -static" ++ else ++ case "${flagsm}" in ++ *le|*gnu|*hk) ++ LDFLAGS="${LDFLAGS} -rdynamic" ++ ;; ++ *fb|*nb) ++ LDFLAGS="${LDFLAGS} -rdynamic -L/usr/local/lib" ++ ;; ++ *ob) ++ LDFLAGS="${LDFLAGS} -rdynamic -Wl,--export-dynamic -Wl,-zwxneeded -L/usr/local/lib" ++ ;; ++ *) ++ ;; ++ esac ++ fi + + case "${flagsm}" in + *le|*gnu) +- LIBS="${LIBS} -lm -ldl ${ncursesLib} -lrt" ++ if [ "$staticbuild" = "yes" ] ; then ++ LIBS="${LIBS} -lm -lrt" ++ else ++ LIBS="${LIBS} -lm -ldl ${ncursesLib} -lrt" ++ fi + ;; + *fb|*ob|*hk) + LIBS="${LIBS} ${iconvLib} -lm ${ncursesLib}" +@@ -916,7 +946,11 @@ if [ "$addflags" = "yes" ] ; then + LIBS="${LIBS} -lm ${cursesLib} -lterminfo" + ;; + *s2) +- LIBS="${LIBS} -lnsl -ldl -lm ${cursesLib} -lrt" ++ if [ "$staticbuild" = "yes" ] ; then ++ LIBS="${LIBS} -lnsl -lm ${cursesLib} -lrt" ++ else ++ LIBS="${LIBS} -lnsl -ldl -lm ${cursesLib} -lrt" ++ fi + ;; + *osx) + LIBS="${LIBS} ${iconvLib} -lm ${ncursesLib}" +@@ -1216,6 +1250,8 @@ installscriptname=$installscriptname + disablecurses=$disablecurses + disableiconv=$disableiconv + disablex11=$disablex11 ++disabledynload=$disabledynload ++foreignLibs=$foreignLibs + enablelibffi=$libffi + preloadBootFiles=$preloadBootFiles + alwaysUseBootFile=$alwaysUseBootFile +diff --git a/tools/gen-static-ffi.sh b/tools/gen-static-ffi.sh +new file mode 100755 +index 00000000..3fb22e56 +--- /dev/null ++++ b/tools/gen-static-ffi.sh +@@ -0,0 +1,61 @@ ++#!/bin/sh ++# gen-static-ffi.sh ++# Generate a C source file that registers exported symbols from static ++# libraries (.a files) via Sforeign_symbol(), enabling FFI access to ++# statically linked libraries. ++# ++# Usage: gen-static-ffi.sh lib1.a [lib2.a ...] > static_ffi.c ++ ++if [ $# -eq 0 ]; then ++ echo "Usage: $0 lib1.a [lib2.a ...] > static_ffi.c" >&2 ++ exit 1 ++fi ++ ++# Verify all input files exist ++for lib in "$@"; do ++ if [ ! -f "$lib" ]; then ++ echo "Error: $lib not found" >&2 ++ exit 1 ++ fi ++done ++ ++# Collect all exported text symbols from the .a files ++# nm --defined-only -g shows only globally defined symbols ++# Filter for type T (text/code) symbols ++syms=$(nm --defined-only -g "$@" 2>/dev/null | awk '$2 == "T" { print $3 }' | sort -u) ++ ++if [ -z "$syms" ]; then ++ echo "Warning: no exported text symbols found in: $*" >&2 ++fi ++ ++cat <<'HEADER' ++/* Generated by gen-static-ffi.sh -- do not edit */ ++#include "scheme.h" ++ ++HEADER ++ ++# Generate extern declarations ++for sym in $syms; do ++ # Skip compiler/runtime internal symbols (leading underscore on some platforms) ++ case "$sym" in ++ _*) continue ;; ++ esac ++ printf 'extern void %s();\n' "$sym" ++done ++ ++cat <<'MIDDLE' ++ ++void static_ffi_init(void) { ++MIDDLE ++ ++# Generate Sforeign_symbol registrations ++for sym in $syms; do ++ case "$sym" in ++ _*) continue ;; ++ esac ++ printf ' Sforeign_symbol("%s", (void *)%s);\n' "$sym" "$sym" ++done ++ ++cat <<'FOOTER' ++} ++FOOTER +-- +2.39.5 (Apple Git-154) + new file mode 100644 --- /dev/null +++ b/vendor/ChezScheme-patches/0002-Harden-FASL-deserialization-FFI-and-build-scripts-ag.patch @@ -0,0 +1,169 @@ +From b3f35405ff20b85ae270f279d9c84f5348e6cc44 Mon Sep 17 00:00:00 2001 +From: Jaime Fournier <jaimef@linbsd.org> +Date: Sat, 21 Mar 2026 18:44:19 -0600 +Subject: [PATCH] Harden FASL deserialization, FFI, and build scripts against + malformed input + +Add bounds/overflow checks in fasl.c for compressed size calculation, +uncompressed size, string building, and record field parsing to prevent +heap corruption from crafted .so/.boot files. Add FFI array size overflow +check, LZ4 size_t-to-INT truncation guards, sprintf->snprintf, strcpy->memcpy, +and quote shell variables in configure script. + +Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> +--- + c/compress-io.c | 5 +++-- + c/fasl.c | 17 ++++++++++++++--- + c/ffi.c | 2 ++ + c/prim5.c | 2 +- + c/scheme.c | 2 +- + configure | 10 +++++----- + 6 files changed, 26 insertions(+), 12 deletions(-) + +diff --git a/c/compress-io.c b/c/compress-io.c +index fcbc989d..82cc9069 100644 +--- a/c/compress-io.c ++++ b/c/compress-io.c +@@ -23,6 +23,7 @@ + #include "lz4hc.h" + #include <fcntl.h> + #include <errno.h> ++#include <limits.h> + + #ifdef WIN32 + #include <io.h> +@@ -429,8 +430,8 @@ static INT glzread_lz4(lz4File_in *lz4, void *buffer, UINT count) { + return -1; + } + +- lz4->in_pos += (INT)in_len; +- lz4->out_len = (INT)out_len; ++ lz4->in_pos += (in_len > INT_MAX ? INT_MAX : (INT)in_len); ++ lz4->out_len = (out_len > INT_MAX ? INT_MAX : (INT)out_len); + lz4->out_pos = 0; + } + } else { +diff --git a/c/fasl.c b/c/fasl.c +index 1ef27694..a2370fa6 100644 +--- a/c/fasl.c ++++ b/c/fasl.c +@@ -460,7 +460,12 @@ static ptr fasl_entry(ptr tc, IFASLCODE situation, faslFile f, ptr externals) { + case fasl_type_lz4: { + ptr result; INT bytes_consumed; + iptr dest_size = S_fasl_uptrin(f, &bytes_consumed); +- iptr src_size = size - (2 + bytes_consumed); /* adjust for u8 compression type, u8 fasl type, and uptr dest_size */ ++ iptr src_size; ++ if (size < (iptr)(2 + bytes_consumed)) ++ S_error1("", "malformed fasl-object found in ~a", f->uf.path); ++ src_size = size - (2 + bytes_consumed); /* adjust for u8 compression type, u8 fasl type, and uptr dest_size */ ++ if (dest_size < 0) ++ S_error1("", "malformed fasl-object found in ~a", f->uf.path); + + if ((uptr)src_size > (uptr)maximum_bytevector_length || + (uptr)dest_size > (uptr)maximum_bytevector_length) +@@ -483,6 +488,8 @@ static ptr fasl_entry(ptr tc, IFASLCODE situation, faslFile f, ptr externals) { + case fasl_type_uncompressed: { + in_f = f; + old_mode = f->buffer_mode; ++ if (size < 2) ++ S_error1("", "malformed fasl-object found in ~a", f->uf.path); + size -= 2; /* adjust for u8 compression type and u8 fasl type */ + if (size < 0) + S_error1("", "invalid fasl uncompressed size found in ~a", f->uf.path); +@@ -1236,15 +1243,19 @@ static void faslin(ptr tc, ptr *x, ptr t, ptr *pstrbuf, faslFile f) { + # define unknown 3 + #endif + static void fasl_record(ptr tc, ptr *x, ptr t, ptr *pstrbuf, faslFile f, iptr size) { +- iptr n; uptr addr; ptr p; UINT padty; ++ iptr n; uptr addr, addr_base, addr_limit; ptr p; UINT padty; + + n = sizein(f); + *x = p = S_record(size_record_inst(size)); + faslin(tc, &RECORDINSTTYPE(p), t, pstrbuf, f); +- addr = (uptr)TO_PTR(&RECORDINSTIT(p, 0)); ++ addr_base = (uptr)TO_PTR(&RECORDINSTIT(p, 0)); ++ addr_limit = addr_base + size; ++ addr = addr_base; + for (; n != 0; n -= 1) { + padty = bytein(f); + addr += padty >> 4; ++ if (addr < addr_base || addr >= addr_limit) ++ S_error1("", "malformed record in fasl object found in ~a", f->uf.path); + switch (padty & 0xf) { + case fasl_fld_ptr: + faslin(tc, TO_VOIDP(addr), t, pstrbuf, f); +diff --git a/c/ffi.c b/c/ffi.c +index c2aa7e79..79858793 100644 +--- a/c/ffi.c ++++ b/c/ffi.c +@@ -253,6 +253,8 @@ ffi_type *decode_type(alloc_state *alloc, ptr type, ffi_abi abi, IBOOL *all_floa + + ffi_prep_cif(&cif, abi, 0, elem_out, NULL); + ++ if (len != 0 && elem_out->size > ((size_t)-1) / len) ++ S_error("decode_type", "ffi array size overflow"); + out->size = elem_out->size * len; + out->alignment = elem_out->alignment; + out->type = FFI_TYPE_STRUCT; +diff --git a/c/prim5.c b/c/prim5.c +index e0fe02ec..f8089917 100644 +--- a/c/prim5.c ++++ b/c/prim5.c +@@ -2228,7 +2228,7 @@ static ptr s_iconv_trouble(HMODULE h, const char *what) { + FreeLibrary(h); + n = strlen(what) + strlen(dll) + 17; + msg = (char *)malloc(n); +- sprintf(msg, "cannot find %s in %s", what, dll); ++ snprintf(msg, n, "cannot find %s in %s", what, dll); + free(dll); + r = Sstring_utf8(msg, -1); + free(msg); +diff --git a/c/scheme.c b/c/scheme.c +index 086e4237..d15c2eb6 100644 +--- a/c/scheme.c ++++ b/c/scheme.c +@@ -651,7 +651,7 @@ static void add_boot(boot_desc *boot, const char *path) { + fprintf(stderr, "boot-file path is too long %s\n", path); + S_abnormal_exit(); + } +- strcpy(boot->path, path); ++ memcpy(boot->path, path, strlen(path) + 1); + } + + static IBOOL check_boot(faslFile f, IBOOL verbose, const char *path) { +diff --git a/configure b/configure +index 5669a4e2..5bc3802f 100755 +--- a/configure ++++ b/configure +@@ -1190,23 +1190,23 @@ if [ "$skipImmediateMakefile" = "" ] ; then + else + makefile_in=Makefile.in + fi +- sed -e 's/$(w)/'$w'/g' "$srcdir"/makefiles/"$makefile_in" > Makefile ++ sed -e 's|$(w)|'"$w"'|g' "$srcdir"/makefiles/"$makefile_in" > Makefile + fi + +-mkdir -p $w ++mkdir -p "$w" + + if [ "$skipImmediateMakefile" = "" ] ; then +- # Stub Zuo script to lanch the real one, using "Makefile" ++ # Stub Zuo script to launch the real one, using "Makefile" + # to locate the workarea: + cp "$srcdir"/makefiles/buildmain.zuo main.zuo + fi + + # Some idea, but in the workarea, so it refers to "workarea.zuo" here: +-cp "$srcdir"/makefiles/workmain.zuo $w/main.zuo ++cp "$srcdir"/makefiles/workmain.zuo "$w"/main.zuo + + # The content of "$w/Mf-config" records configuration decisions, + # and the Zuo build script takes it from there +-cat > $w/Mf-config << END ++cat > "$w"/Mf-config << END + srcdir=$srcdir + upsrcdir=$upsrcdir + m=$m +-- +2.39.5 (Apple Git-154) + new file mode 100644 --- /dev/null +++ b/vendor/ChezScheme-patches/0003-Add-bounds-checks-in-vfasl-deserialization-fix-HPUX-.patch @@ -0,0 +1,81 @@ +From aaa82190e95cd09544733458563704af9330ebed Mon Sep 17 00:00:00 2001 +From: Jaime Fournier <jaimef@linbsd.org> +Date: Sat, 21 Mar 2026 19:00:47 -0600 +Subject: [PATCH] Add bounds checks in vfasl deserialization, fix HPUX typo, + harden path_append + +- vfasl.c: bounds check in find_pointer_from_offset to prevent OOB read + from crafted vfasl offsets exceeding vspace_offsets array +- vfasl.c: bounds check in lookup_singleton to reject invalid singleton + indices from malformed vfasl data +- foreign.c: fix undefined variable 'name' -> 's' in HPUX branch +- self-exe.c: integer overflow check in path_append malloc size calculation +- findings.md: add round 2 findings including parallel GC races, signal + handler safety, and oblist resize race (unfixed design-level issues) + +Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> +--- + c/foreign.c | 2 +- + c/self-exe.c | 6 +++++- + c/vfasl.c | 7 ++++++- + 3 files changed, 12 insertions(+), 3 deletions(-) + +diff --git a/c/foreign.c b/c/foreign.c +index 42c7a6e6..14896d93 100644 +--- a/c/foreign.c ++++ b/c/foreign.c +@@ -191,7 +191,7 @@ void Sforeign_symbol(const char *s, void *v) { + tc_mutex_acquire(); + + #ifdef HPUX +- v = proc2entry(v,name); ++ v = proc2entry(v,s); + #endif + + if ((x = lookup(s)) == addr_to_ptr(0)) { +diff --git a/c/self-exe.c b/c/self-exe.c +index 500b9441..3ebe5098 100644 +--- a/c/self-exe.c ++++ b/c/self-exe.c +@@ -254,7 +254,11 @@ static char *get_self_path_platform() { return NULL; } + static char *path_append(const char *s1, const char *s2) { + size_t l1 = strlen(s1); + size_t l2 = strlen(s2); +- char *r = (char *)malloc(l1 + l2 + 2); ++ size_t alloc_size = l1 + l2 + 2; ++ char *r; ++ if (alloc_size < l1 || alloc_size < l2) ++ return NULL; ++ r = (char *)malloc(alloc_size); + if (r == NULL) { + return NULL; + } +diff --git a/c/vfasl.c b/c/vfasl.c +index 066b993a..c321b846 100644 +--- a/c/vfasl.c ++++ b/c/vfasl.c +@@ -582,8 +582,11 @@ static ptr find_pointer_from_offset(uptr p_off, ptr *vspaces, uptr *vspace_offse + ITYPE t = TYPEBITS(p_off); + + p_off = (uptr)UNTYPE(p_off, t); +- while (p_off >= vspace_offsets[s+1]) ++ while (p_off >= vspace_offsets[s+1]) { + s++; ++ if (s >= vspaces_count) ++ S_error("vfasl-read", "invalid pointer offset"); ++ } + + return TYPE(ptr_add(vspaces[s], p_off - vspace_offsets[s]), t); + } +@@ -610,6 +613,8 @@ static ptr *singleton_refs[] = { &S_G.null_string, + static ptr lookup_singleton(iptr which) { + ptr v; + ++ if (which < 1 || which > (iptr)(sizeof(singleton_refs) / sizeof(singleton_refs[0]))) ++ S_error("vfasl-read", "invalid singleton index"); + v = *(singleton_refs[which-1]); + + if (v == Sfalse) { +-- +2.39.5 (Apple Git-154) + new file mode 100644 --- /dev/null +++ b/vendor/ChezScheme-patches/0004-hard-links-are-for-suckers.patch @@ -0,0 +1,30 @@ +From 5f9c697ccbe7b37b6ad27bd5315afee9ac68793c Mon Sep 17 00:00:00 2001 +From: Jaime Fournier <jaimef@linbsd.org> +Date: Fri, 3 Apr 2026 17:44:46 -0600 +Subject: [PATCH] hard links are for suckers + +--- + makefiles/install.zuo | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + +diff --git a/makefiles/install.zuo b/makefiles/install.zuo +index d0a89a09..804734e3 100644 +--- a/makefiles/install.zuo ++++ b/makefiles/install.zuo +@@ -179,12 +179,8 @@ + (shell/wait* "rm" "-f" f)) + (define (rm-rf d) + (shell/wait* "rm" "-rf" d)) +- (define haiku? (glob-match? "*hk" m)) + (define (ln-f from to) +- (if haiku? +- ;; no hard links on BeFS +- (shell/wait* "ln" "-s" from to) +- (shell/wait* "ln" "-f" from to))) ++ (shell/wait* "cp" "-f" from to))