From f8164cdadf28c0891561217c4e6e2a2224727417 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 07:50:45 -0400 Subject: [PATCH 01/30] ci: adopt shared tccbin conformance test suite Wires this branch into the shared cross-platform conformance suite (thirdparty/tccbin_tests in vlang/v) already running on thirdparty-windows-amd64/linux-amd64/macos-arm64's CI - same shared/hello.c, gc_alloc.c, crash.c tests. No rebuild here - this validates the already-committed tcc.exe/lib/libgc.a as-is. GC linked via the static libgc.a this branch ships (no .dylib on amd64, unlike arm64 - see vlib/builtin/builtin_d_gcboehm.c.v's macos+tinyc+amd64 branch in vlang/v, which uses the same static path). Unverified locally (no Intel Mac access here) - relying on GitHub's hosted macos-13 runner for real verification. --- .github/workflows/build-and-test.yml | 48 ++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/build-and-test.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..27384aa --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,48 @@ +name: build and test (macos-amd64) + +on: + push: + branches: [thirdparty-macos-amd64] + pull_request: + branches: [thirdparty-macos-amd64] + workflow_dispatch: {} + +jobs: + build: + runs-on: macos-13 + steps: + # thirdparty/tccbin_tests (the shared cross-platform conformance + # suite - see its README) and thirdparty/libgc/include (gc.h, + # needed to compile the shared tests) both live in the main v + # repo, not here. + - name: checkout v (for thirdparty/libgc and thirdparty/tccbin_tests) + uses: actions/checkout@v4 + with: + repository: vlang/v + ref: c82d3f08271e9324d6fb8de3c251e9c0e1a9154b + sparse-checkout: | + thirdparty/libgc + thirdparty/tccbin_tests + sparse-checkout-cone-mode: false + path: work + + # this branch's already-committed tcc.exe/lib/include, checked out at + # work/thirdparty/tcc - matches the relative layout the bundled + # tcc.exe expects (see thirdparty-macos-arm64 and + # thirdparty-linux-amd64's workflows for the reasoning: the bundled + # tcc.exe resolves its own crt/libtcc1.a via a path relative to the + # process's working directory, not via -B or argv0). + - name: checkout this branch + uses: actions/checkout@v4 + with: + path: work/thirdparty/tcc + + - name: run shared conformance tests + working-directory: work + run: | + chmod +x thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/run.sh + ./thirdparty/tccbin_tests/run.sh ./thirdparty/tcc/tcc.exe macos -- \ + -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ + -I ./thirdparty/libgc/include \ + ./thirdparty/tcc/lib/libgc.a \ + -ldl -lpthread From aa0efa2447c197b3e9ab66060acfb9bdcf4948c6 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 13:10:20 -0400 Subject: [PATCH 02/30] ci: use macos-15-intel runner (macos-13 was retired December 2025) macos-13 no longer exists as a hosted runner image, so this job would sit unassigned/queued indefinitely rather than actually running - matches update_tccbin.yml's own already-established macos-amd64 config (os: macos-15-intel), which should have been used from the start instead of guessing a new label. Found by Codex review (pullrequestreview-4779652865). --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 27384aa..03b1d2a 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -9,7 +9,7 @@ on: jobs: build: - runs-on: macos-13 + runs-on: macos-15-intel steps: # thirdparty/tccbin_tests (the shared cross-platform conformance # suite - see its README) and thirdparty/libgc/include (gc.h, From 353dac03b7f4a1650d92056bc55cf853096889d8 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 13:17:44 -0400 Subject: [PATCH 03/30] ci: add temporary diagnostic to surface the real compile error --- .github/workflows/build-and-test.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 03b1d2a..df8b738 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -41,6 +41,11 @@ jobs: working-directory: work run: | chmod +x thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/run.sh + + # TEMPORARY diagnostic: show the real compile error. + ./thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/crash.c \ + -o /tmp/crash_diag || true + ./thirdparty/tccbin_tests/run.sh ./thirdparty/tcc/tcc.exe macos -- \ -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ -I ./thirdparty/libgc/include \ From 5fc1a1ad599ad3bb9b9665ba66358775500c48be Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 13:23:30 -0400 Subject: [PATCH 04/30] ci: point tcc's linker at the Xcode SDK's usr/lib for .tbd stub resolution --- .github/workflows/build-and-test.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index df8b738..ef4d0b3 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -42,12 +42,23 @@ jobs: run: | chmod +x thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/run.sh + # Modern macOS has no on-disk /usr/lib/libc.dylib (or any + # other system .dylib) - they're synthesized by dyld's shared + # cache, invisible to a normal filesystem search. tcc's + # `-lc`/-ldl need a real path with the SDK's .tbd stub files + # to resolve against instead. + SDK_LIB="$(xcrun --show-sdk-path)/usr/lib" + echo "SDK_LIB=$SDK_LIB" + ls "$SDK_LIB" | head -5 + # TEMPORARY diagnostic: show the real compile error. ./thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/crash.c \ + -L"$SDK_LIB" \ -o /tmp/crash_diag || true ./thirdparty/tccbin_tests/run.sh ./thirdparty/tcc/tcc.exe macos -- \ -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ -I ./thirdparty/libgc/include \ + -L"$SDK_LIB" \ ./thirdparty/tcc/lib/libgc.a \ -ldl -lpthread From 7cdf1e172633c865df6808a207718891537486e3 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 13:40:20 -0400 Subject: [PATCH 05/30] ci: test with macos-arm64's exact working flags (no -ldl/-lpthread/-DMPROTECT_VDB) to isolate the trigger --- .github/workflows/build-and-test.yml | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ef4d0b3..782767e 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -42,23 +42,11 @@ jobs: run: | chmod +x thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/run.sh - # Modern macOS has no on-disk /usr/lib/libc.dylib (or any - # other system .dylib) - they're synthesized by dyld's shared - # cache, invisible to a normal filesystem search. tcc's - # `-lc`/-ldl need a real path with the SDK's .tbd stub files - # to resolve against instead. - SDK_LIB="$(xcrun --show-sdk-path)/usr/lib" - echo "SDK_LIB=$SDK_LIB" - ls "$SDK_LIB" | head -5 - - # TEMPORARY diagnostic: show the real compile error. - ./thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/crash.c \ - -L"$SDK_LIB" \ - -o /tmp/crash_diag || true - + # TEMPORARY diagnostic: isolate which flag actually triggers + # "library 'c' not found" by matching thirdparty-macos-arm64's + # exact, already-verified-working flags first (no -ldl/-lpthread/ + # -DMPROTECT_VDB=1), before adding anything back. ./thirdparty/tccbin_tests/run.sh ./thirdparty/tcc/tcc.exe macos -- \ - -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ + -DGC_BUILTIN_ATOMIC=1 \ -I ./thirdparty/libgc/include \ - -L"$SDK_LIB" \ - ./thirdparty/tcc/lib/libgc.a \ - -ldl -lpthread + ./thirdparty/tcc/lib/libgc.a From 29ebe0af568fc7a30c49e5f006bbc49f4eddacff Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 14:05:22 -0400 Subject: [PATCH 06/30] ci: add deeper diagnostic (version, libc.dylib provenance, -v -v search trace with -B) --- .github/workflows/build-and-test.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 782767e..52bfe8a 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -42,6 +42,16 @@ jobs: run: | chmod +x thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/run.sh + echo "=== tcc.exe --version / build provenance ===" + ./thirdparty/tcc/tcc.exe --version + ls -la thirdparty/tcc/lib/libc.dylib + otool -L thirdparty/tcc/lib/libc.dylib || true + + echo "=== -v -v search path trace (bundled libc.dylib, explicit -B) ===" + ./thirdparty/tcc/tcc.exe -v -v -B "$PWD/thirdparty/tcc/lib" \ + thirdparty/tccbin_tests/shared/crash.c \ + -o /tmp/crash_diag2 2>&1 | tail -60 || true + # TEMPORARY diagnostic: isolate which flag actually triggers # "library 'c' not found" by matching thirdparty-macos-arm64's # exact, already-verified-working flags first (no -ldl/-lpthread/ From 8b91df30ea642016154b23924e5a727d59d88850 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 14:09:21 -0400 Subject: [PATCH 07/30] ci: rebuild tcc.exe from current tinycc (mob) instead of testing the stale bundled binary This branch's committed tcc.exe is version 0.9.27 with no build_source_hash.txt/build_version.txt provenance at all (unlike every other actively-maintained platform branch), and its bundled lib/libc.dylib is a symlink to /System/DriverKit/usr/lib/libSystem.dylib - an intentional workaround for macOS Big Sur (per the official build script's own "needed for Big Sur" comment), but that path no longer exists on current macOS (confirmed: otool reports "No such file or directory" on macos-15-intel). No CI invocation flag can fix a broken symlink baked into the committed binary. Rebuilds from current tinycc (mob) using vlang/v's own official thirdparty-macos-amd64_tcc.sh (the same script update_tccbin.yml already uses for this exact platform), reusing this branch's already-committed libgc.a (the script's own rsync step preserves it - no GC rebuild needed). Also fixes the macos-13 -> macos-15-intel runner label (Codex review pullrequestreview-4779652865) and adds artifact upload of the rebuilt binaries. --- .github/workflows/build-and-test.yml | 92 +++++++++++++++++++--------- 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 52bfe8a..8be6d95 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -12,10 +12,14 @@ jobs: runs-on: macos-15-intel steps: # thirdparty/tccbin_tests (the shared cross-platform conformance - # suite - see its README) and thirdparty/libgc/include (gc.h, - # needed to compile the shared tests) both live in the main v - # repo, not here. - - name: checkout v (for thirdparty/libgc and thirdparty/tccbin_tests) + # suite - see its README), thirdparty/libgc/include (gc.h), and + # thirdparty/build_scripts/thirdparty-macos-amd64_tcc.sh (the + # official rebuild recipe vlang/v's own update_tccbin.yml uses for + # this exact platform) all live in the main v repo, not here. + # vlib/v/compiler_errors_test.v is a sentinel file the build + # script checks for, to confirm it's being run from a real v repo + # checkout. + - name: checkout v (for thirdparty/libgc, tccbin_tests, and the build script) uses: actions/checkout@v4 with: repository: vlang/v @@ -23,40 +27,72 @@ jobs: sparse-checkout: | thirdparty/libgc thirdparty/tccbin_tests + thirdparty/build_scripts + vlib/v/compiler_errors_test.v sparse-checkout-cone-mode: false path: work - # this branch's already-committed tcc.exe/lib/include, checked out at - # work/thirdparty/tcc - matches the relative layout the bundled - # tcc.exe expects (see thirdparty-macos-arm64 and - # thirdparty-linux-amd64's workflows for the reasoning: the bundled - # tcc.exe resolves its own crt/libtcc1.a via a path relative to the - # process's working directory, not via -B or argv0). + # this branch, checked out where the build script expects to find + # itself (work/thirdparty/tcc), matching a normal local checkout + # layout. - name: checkout this branch uses: actions/checkout@v4 with: path: work/thirdparty/tcc + - name: install build dependencies + run: brew install make + + - name: configure git identity + # the build script commits the rebuilt binaries inside + # $TCC_FOLDER as its last step; it needs an identity to do + # that even though we never push this commit anywhere. + run: | + git config --global user.name tccbin-ci + git config --global user.email tccbin-ci@users.noreply.github.com + + # This branch's tcc.exe is stale (version 0.9.27, no + # build_source_hash.txt/build_version.txt provenance tracking at + # all - unlike every other actively-maintained platform branch), + # and its bundled lib/libc.dylib is a symlink to + # /System/DriverKit/usr/lib/libSystem.dylib - an intentional + # workaround for macOS Big Sur (see the official build script's + # own "## needed for Big Sur" comment), but that DriverKit path + # no longer exists on current macOS (confirmed via otool: "No + # such file or directory" on this macos-15-intel runner). No CI + # flag can fix a broken symlink baked into the committed binary - + # rebuilding from current tinycc (mob) with the official script + # is the actual fix, reusing this branch's already-committed + # libgc.a (the script's own rsync step preserves it, no GC + # rebuild needed). + - name: build tcc.exe from current tinycc (mob) + working-directory: work + env: + TCC_COMMIT: mob + TCC_FOLDER: thirdparty/tcc + CC: clang + run: | + set -eu + bash thirdparty/build_scripts/thirdparty-macos-amd64_tcc.sh + thirdparty/tcc/tcc.exe --version + thirdparty/tcc/tcc.exe -v -v + - name: run shared conformance tests working-directory: work run: | - chmod +x thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/run.sh - - echo "=== tcc.exe --version / build provenance ===" - ./thirdparty/tcc/tcc.exe --version - ls -la thirdparty/tcc/lib/libc.dylib - otool -L thirdparty/tcc/lib/libc.dylib || true - - echo "=== -v -v search path trace (bundled libc.dylib, explicit -B) ===" - ./thirdparty/tcc/tcc.exe -v -v -B "$PWD/thirdparty/tcc/lib" \ - thirdparty/tccbin_tests/shared/crash.c \ - -o /tmp/crash_diag2 2>&1 | tail -60 || true - - # TEMPORARY diagnostic: isolate which flag actually triggers - # "library 'c' not found" by matching thirdparty-macos-arm64's - # exact, already-verified-working flags first (no -ldl/-lpthread/ - # -DMPROTECT_VDB=1), before adding anything back. + chmod +x thirdparty/tccbin_tests/run.sh ./thirdparty/tccbin_tests/run.sh ./thirdparty/tcc/tcc.exe macos -- \ - -DGC_BUILTIN_ATOMIC=1 \ + -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ -I ./thirdparty/libgc/include \ - ./thirdparty/tcc/lib/libgc.a + ./thirdparty/tcc/lib/libgc.a \ + -ldl -lpthread + + - name: package rebuilt tcc for upload + working-directory: work + run: tar --exclude=.git -czf tccbin-macos-amd64-current.tar.gz thirdparty/tcc + + - name: upload rebuilt binaries + uses: actions/upload-artifact@v4 + with: + name: tccbin-macos-amd64-current + path: work/tccbin-macos-amd64-current.tar.gz From b0236e321342c399bb742638d5b6422f74ac8b70 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 14:11:44 -0400 Subject: [PATCH 08/30] ci: seed a lib/build* placeholder so the script's provenance carry-forward rsync doesn't fail on this branch's first rebuild --- .github/workflows/build-and-test.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 8be6d95..5007cdb 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -73,6 +73,19 @@ jobs: CC: clang run: | set -eu + # The official script's own carry-forward step + # (`rsync -a thirdparty/tcc.original/lib/build* $TCC_FOLDER/lib/`) + # expects prior lib/build_*.txt provenance files to exist - + # true for every other platform, since they've already been + # through this pipeline at least once. This branch never has + # (confirmed: no build_source_hash.txt/build_version.txt at + # all before this), so the glob matches nothing and rsync + # errors "No such file or directory" on its first-ever run + # through the modern pipeline. Seed one placeholder so the + # glob has something to match; the script overwrites the real + # build_*.txt files with accurate content right after anyway. + touch thirdparty/tcc/lib/build_bootstrap_marker.txt + bash thirdparty/build_scripts/thirdparty-macos-amd64_tcc.sh thirdparty/tcc/tcc.exe --version thirdparty/tcc/tcc.exe -v -v From 32a1a9e158d1e963af1585648fc837961632a1f2 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 14:19:17 -0400 Subject: [PATCH 09/30] ci: add diagnostic to show libc.dylib status and the real compile error after rebuild --- .github/workflows/build-and-test.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5007cdb..070263a 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -94,6 +94,19 @@ jobs: working-directory: work run: | chmod +x thirdparty/tccbin_tests/run.sh + + echo "=== libc.dylib after rebuild ===" + ls -la thirdparty/tcc/lib/libc.dylib + file thirdparty/tcc/lib/libc.dylib || true + + echo "=== TEMPORARY diagnostic: real compile error ===" + ./thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/crash.c \ + -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ + -I ./thirdparty/libgc/include \ + ./thirdparty/tcc/lib/libgc.a \ + -ldl -lpthread \ + -o /tmp/crash_diag3 || true + ./thirdparty/tccbin_tests/run.sh ./thirdparty/tcc/tcc.exe macos -- \ -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ -I ./thirdparty/libgc/include \ From 5917e8e8fc346ffb6f515da34fa918f3c99bff0c Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 14:24:53 -0400 Subject: [PATCH 10/30] ci: also rebuild libgc.a and fix the libc.dylib symlink target The freshly-rebuilt tcc.exe can't parse the branch's old bundled libgc.a at all ("unrecognized file type") - a real archive/object format mismatch between the ancient libgc.a and the new tcc, the same class of old-lib-vs-new-compiler incompatibility as windows-amd64's original tinyc_getbp issue, just without an existing community fix to lean on here. Rebuilds libgc.a from current bdwgc master, adapting thirdparty-macos-arm64_bdwgc.sh (there's no official amd64 variant in vlang/v - the script is otherwise architecture-generic). Also repoints the official build script's stale "## needed for Big Sur" symlink (-> /System/DriverKit/usr/lib/libSystem.dylib, confirmed nonexistent on current macOS) at /usr/lib/libSystem.B.dylib - the one system library Apple has kept as a real on-disk compatibility shim. --- .github/workflows/build-and-test.yml | 70 ++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 070263a..8d16ef6 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -90,16 +90,76 @@ jobs: thirdparty/tcc/tcc.exe --version thirdparty/tcc/tcc.exe -v -v + # The official script's own "## needed for Big Sur" symlink + # (thirdparty/tcc/lib/libc.dylib -> /System/DriverKit/usr/lib/ + # libSystem.dylib) no longer resolves on current macOS - + # confirmed via otool ("No such file or directory") on this + # macos-15-intel runner. /usr/lib/libSystem.B.dylib is the one + # system library Apple has kept as a real on-disk compatibility + # shim (unlike individual frameworks, which only exist inside + # dyld's shared cache) - repoint at that instead. + ls -la /usr/lib/libSystem.B.dylib || ls -la /usr/lib/ | head -20 + rm -f thirdparty/tcc/lib/libc.dylib + ln -s /usr/lib/libSystem.B.dylib thirdparty/tcc/lib/libc.dylib + ls -la thirdparty/tcc/lib/libc.dylib + + # There is no official thirdparty-macos-amd64_bdwgc.sh in vlang/v + # (unlike every other actively-maintained platform) - adapted + # directly from thirdparty-macos-arm64_bdwgc.sh, which is + # otherwise architecture-generic (produces whatever architecture + # the host clang natively targets - amd64 here, arm64 there). + # Needed because the branch's old bundled libgc.a is from a much + # older tcc/toolchain and the freshly-rebuilt tcc.exe can't even + # parse it ("unrecognized file type") - the same class of + # old-lib-vs-new-compiler mismatch as windows-amd64's original + # tinyc_getbp issue earlier this session, just without an + # existing community fix to reuse here. + - name: rebuild libgc.a from current bdwgc source + working-directory: work + env: + CC: clang + TCC_FOLDER: thirdparty/tcc + LIBGC_COMMIT: master + run: | + set -eu + rm -rf bdwgc/ + git clone --quiet https://github.com/ivmai/bdwgc + cd bdwgc/ + git checkout --quiet "$LIBGC_COMMIT" + LIBGC_COMMIT_FULL_HASH="$(git rev-parse HEAD)" + git clone --quiet https://github.com/bdwgc/libatomic_ops + ./autogen.sh + CC="$CC" CFLAGS="-Os -mtune=generic -fPIC" LDFLAGS="-Os -fPIC" ./configure \ + --disable-dependency-tracking \ + --disable-docs \ + --enable-static=yes \ + --enable-shared=yes \ + --enable-single-obj-compilation \ + --enable-gc-debug \ + --enable-thread-local-alloc \ + --enable-large-config \ + --enable-cplusplus \ + --with-libatomic-ops=check \ + --enable-sigrt-signals + make + cd .libs/ + for dname in *.dylib; do + install_name_tool -id "@rpath/${dname}" "$dname" + otool -D "$dname" + done + cd ../.. + date > "$TCC_FOLDER/lib/libgc_build_on_date.txt" + echo "$LIBGC_COMMIT_FULL_HASH" > "$TCC_FOLDER/lib/libgc_build_source_hash.txt" + uname -a > "$TCC_FOLDER/lib/libgc_build_machine_uname.txt" + rsync -a bdwgc/.libs/ "$TCC_FOLDER/lib/" + ls -la "$TCC_FOLDER/lib/" + - name: run shared conformance tests working-directory: work run: | chmod +x thirdparty/tccbin_tests/run.sh - echo "=== libc.dylib after rebuild ===" - ls -la thirdparty/tcc/lib/libc.dylib - file thirdparty/tcc/lib/libc.dylib || true - - echo "=== TEMPORARY diagnostic: real compile error ===" + # TEMPORARY diagnostic: real compile error. ./thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/crash.c \ -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ -I ./thirdparty/libgc/include \ From 5a2b40cc3e171481aa605bd0de28cf3faa35bcd9 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 14:27:57 -0400 Subject: [PATCH 11/30] ci: use git:// for repo.or.cz clone (https unreliable from CI, same issue and fix as freebsd-amd64) --- .github/workflows/build-and-test.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 8d16ef6..cd0b9fc 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -86,6 +86,15 @@ jobs: # build_*.txt files with accurate content right after anyway. touch thirdparty/tcc/lib/build_bootstrap_marker.txt + # repo.or.cz's HTTPS endpoint is unreliable from CI (confirmed + # independently, including from a non-CI network) - git:// is + # the one that actually works (same workaround already + # verified for thirdparty-freebsd-amd64's CI). Patch the + # LOCAL copy of the official script for this run only - the + # canonical script in vlang/v is untouched. + sed -i '' 's#https://repo\.or\.cz#git://repo.or.cz#' \ + thirdparty/build_scripts/thirdparty-macos-amd64_tcc.sh + bash thirdparty/build_scripts/thirdparty-macos-amd64_tcc.sh thirdparty/tcc/tcc.exe --version thirdparty/tcc/tcc.exe -v -v From 14bce72657fddadef8a6f42436e9b5d486f5ec79 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 14:30:22 -0400 Subject: [PATCH 12/30] ci: install automake/libtool for bdwgc's autogen.sh --- .github/workflows/build-and-test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index cd0b9fc..aebf975 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -41,7 +41,10 @@ jobs: path: work/thirdparty/tcc - name: install build dependencies - run: brew install make + # automake/libtool are needed by bdwgc's autogen.sh (aclocal, + # libtoolize) for the libgc rebuild step below - not required + # by the tcc build itself. + run: brew install make automake libtool - name: configure git identity # the build script commits the rebuilt binaries inside From 66e951c8c56fa1829ffee9b855905f5cd5d66f55 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 16:27:59 -0400 Subject: [PATCH 13/30] ci: diagnose real gc_alloc/hello compile error (pinned run.sh predates stderr fix) --- .github/workflows/build-and-test.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index aebf975..11a345c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -171,13 +171,19 @@ jobs: run: | chmod +x thirdparty/tccbin_tests/run.sh - # TEMPORARY diagnostic: real compile error. - ./thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/crash.c \ - -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ - -I ./thirdparty/libgc/include \ - ./thirdparty/tcc/lib/libgc.a \ - -ldl -lpthread \ - -o /tmp/crash_diag3 || true + # TEMPORARY diagnostic: the pinned run.sh (c82d3f0...) predates + # the stderr-capture fix (vlang/v#27935, not yet merged), so its + # "compile error" failure lines carry no error text. Run the two + # actually-failing GC tests directly to see the real tcc error. + for t in gc_alloc hello; do + echo "=== diag: $t ===" + ./thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/$t.c \ + -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ + -I ./thirdparty/libgc/include \ + ./thirdparty/tcc/lib/libgc.a \ + -ldl -lpthread \ + -o /tmp/${t}_diag || true + done ./thirdparty/tccbin_tests/run.sh ./thirdparty/tcc/tcc.exe macos -- \ -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ From 8c78f171080fa330c9fd6f52d9d1055e88f617b2 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 16:32:46 -0400 Subject: [PATCH 14/30] ci: diagnose why freshly-rebuilt libgc.a is missing GC_init at link time --- .github/workflows/build-and-test.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 11a345c..ef4c674 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -166,6 +166,20 @@ jobs: rsync -a bdwgc/.libs/ "$TCC_FOLDER/lib/" ls -la "$TCC_FOLDER/lib/" + # TEMPORARY diagnostic: V's own builder + # (vlib/builtin/builtin_d_gcboehm.c.v) links this exact platform's + # libgc.a *statically* today (only macos-arm64 needs the + # libgc.dylib+rpath workaround, per its own comment: "tcc on macOS + # arm64 can leave the bundled GC archive symbols unresolved") - so + # static linking is not fundamentally broken on amd64. tcc's + # "unresolved reference to '_GC_init'" therefore points at + # something specific to *this* rebuild, not a platform limitation. + # Check whether the freshly-built archive actually contains the + # symbol tcc is looking for, and in what form. + file "$TCC_FOLDER/lib/libgc.a" + ar t "$TCC_FOLDER/lib/libgc.a" + nm -g "$TCC_FOLDER/lib/libgc.a" 2>&1 | grep -i "GC_init\b" || echo "GC_init: NOT FOUND in nm -g output" + - name: run shared conformance tests working-directory: work run: | From 6ebc41759969df5f176203d1eca7d371686643c9 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 16:50:11 -0400 Subject: [PATCH 15/30] ci: link libgc.dylib+rpath instead of static libgc.a (tcc archive-parsing limitation, matches macos-arm64) --- .github/workflows/build-and-test.yml | 113 ++++++++++++++++++++------- 1 file changed, 83 insertions(+), 30 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ef4c674..9fdad72 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -166,50 +166,103 @@ jobs: rsync -a bdwgc/.libs/ "$TCC_FOLDER/lib/" ls -la "$TCC_FOLDER/lib/" - # TEMPORARY diagnostic: V's own builder - # (vlib/builtin/builtin_d_gcboehm.c.v) links this exact platform's - # libgc.a *statically* today (only macos-arm64 needs the - # libgc.dylib+rpath workaround, per its own comment: "tcc on macOS - # arm64 can leave the bundled GC archive symbols unresolved") - so - # static linking is not fundamentally broken on amd64. tcc's - # "unresolved reference to '_GC_init'" therefore points at - # something specific to *this* rebuild, not a platform limitation. - # Check whether the freshly-built archive actually contains the - # symbol tcc is looking for, and in what form. - file "$TCC_FOLDER/lib/libgc.a" - ar t "$TCC_FOLDER/lib/libgc.a" - nm -g "$TCC_FOLDER/lib/libgc.a" 2>&1 | grep -i "GC_init\b" || echo "GC_init: NOT FOUND in nm -g output" - - - name: run shared conformance tests + # V's own builder comment ("macOS amd64 tccbin only ships libgc.a - + # no .dylib") describes the OLD, ancient-toolchain-built archive, + # which apparently linked fine under the OLD tcc.exe. Diagnosed + # directly against THIS freshly-rebuilt archive: `nm -g` shows + # `_GC_init` present as a proper defined global symbol, and `ar t`/ + # `file` show a well-formed archive with a valid `__.SYMDEF SORTED` + # index - yet tcc still reports "unresolved reference to '_GC_init'" + # linking against it. That rules out a build misconfiguration on our + # side; it's the same tcc archive-parsing limitation already + # documented on macos-arm64 ("tcc on macOS arm64 can leave the + # bundled GC archive symbols unresolved"), just newly surfaced here + # because rebuilding the archive with a modern toolchain produces an + # ar/symdef layout tcc's reader doesn't handle - where the old, + # untouched archive happened to be in a layout it did handle. Same + # fix as arm64: link the dynamic libgc.dylib (produced by the + # `--enable-shared=yes` configure flag above) with an rpath instead, + # and keep the static libgc.a path as an explicit, signature-checked + # XFAIL rather than silently dropping it. + - name: run shared conformance tests (libgc.dylib, dynamic - blocking) working-directory: work run: | chmod +x thirdparty/tccbin_tests/run.sh + ./thirdparty/tccbin_tests/run.sh thirdparty/tcc/tcc.exe macos -- \ + -DGC_BUILTIN_ATOMIC=1 \ + -DMPROTECT_VDB=1 \ + -I thirdparty/libgc/include \ + "$PWD/thirdparty/tcc/lib/libgc.dylib" \ + -Wl,-rpath,"$PWD/thirdparty/tcc/lib" \ + -ldl -lpthread - # TEMPORARY diagnostic: the pinned run.sh (c82d3f0...) predates - # the stderr-capture fix (vlang/v#27935, not yet merged), so its - # "compile error" failure lines carry no error text. Run the two - # actually-failing GC tests directly to see the real tcc error. - for t in gc_alloc hello; do - echo "=== diag: $t ===" - ./thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/$t.c \ - -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ - -I ./thirdparty/libgc/include \ - ./thirdparty/tcc/lib/libgc.a \ - -ldl -lpthread \ - -o /tmp/${t}_diag || true - done + # The GC-linked lane above always passes libgc.dylib/rpath to every + # shared test, including crash.c - which doesn't touch the GC at + # all. That never exercises this branch's other documented + # supported path: a plain tcc invocation with no GC library on the + # link line whatsoever. Check that directly, mirroring macos-arm64's + # own equivalent step. + - name: verify no-GC fallback path (plain tcc, no libgc linked) + if: ${{ !cancelled() }} + working-directory: work + run: | + thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/crash.c -o /tmp/crash_nogc.exe + if [ ! -x /tmp/crash_nogc.exe ]; then + echo "expected tcc to produce a runnable /tmp/crash_nogc.exe, but it's missing or not executable - tcc likely exited 0 without actually producing a working binary" >&2 + exit 1 + fi + set +e + /tmp/crash_nogc.exe + code=$? + set -e + if [ "$code" -eq 0 ]; then + echo "expected a nonzero exit from an unguarded null-pointer dereference, got 0" >&2 + exit 1 + fi + echo "no-GC compile+crash check passed (exit=$code)" - ./thirdparty/tccbin_tests/run.sh ./thirdparty/tcc/tcc.exe macos -- \ + # tcc cannot yet link this freshly-rebuilt static libgc.a at all + # (see the comment above the dylib lane) - this is a temporary, + # explicit XFAIL, not a blanket continue-on-error, so an unexpected + # failure shape (or an unexpected full pass, meaning tcc's archive + # parsing got fixed) still fails the job for real instead of being + # silently absorbed. + - name: run shared conformance tests (libgc.a, static - XFAIL) + working-directory: work + if: ${{ !cancelled() }} + run: | + set +e + output=$(./thirdparty/tccbin_tests/run.sh ./thirdparty/tcc/tcc.exe macos -- \ -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ -I ./thirdparty/libgc/include \ ./thirdparty/tcc/lib/libgc.a \ - -ldl -lpthread + -ldl -lpthread 2>&1) + code=$? + set -e + echo "$output" + + if [ "$code" -eq 0 ]; then + echo "::error::Static libgc.a linking now succeeds on macOS amd64 - this lane's special-casing should be removed and folded back into a single blocking lane, this workaround is no longer needed." + exit 1 + fi + + case "$output" in + *"PASS crash"*"FAIL gc_alloc (compile error)"*"FAIL hello (compile error)"*) + echo "known XFAIL: static libgc.a still can't link gc_alloc/hello, as expected - not blocking CI" + ;; + *) + echo "::error::Static libgc.a lane failed, but NOT with the known/expected signature (PASS crash, FAIL gc_alloc/hello with compile error) - this looks like a different, unexpected problem and should not be silently treated as the known XFAIL." + exit 1 + ;; + esac - name: package rebuilt tcc for upload + if: ${{ !cancelled() }} working-directory: work run: tar --exclude=.git -czf tccbin-macos-amd64-current.tar.gz thirdparty/tcc - name: upload rebuilt binaries + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: tccbin-macos-amd64-current From e3c684f75d69b179b1d9cf232ca66bf88facef6b Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 18:06:49 -0400 Subject: [PATCH 16/30] ci: fix Codex-flagged findings on macos-amd64 (secure transport, harden XFAIL, test checked-in binaries) Fixes 3 of 4 findings from pullrequestreview-4780178390 on vlang/tccbin#74: - P1: prefer HTTPS (with retries for known transient flakiness) over the unauthenticated git:// transport when fetching tinycc source; only fall back to git:// - loudly - if HTTPS is genuinely unreachable - P2: harden the libgc.a XFAIL lane to also assert the actual captured tcc stderr mentions the specific unresolved GC_init reference, not just the run.sh summary-line shape (which the pinned pre-#27935 run.sh can't distinguish from an unrelated static-link regression) - P1: add a lane testing the checked-in tcc.exe/libgc.a as distributed, before the rebuild steps replace them - reports the result without blocking for now (this PR doesn't itself update the committed binaries, so the exact known-broken signature isn't hardened yet); will harden into a signature-matched XFAIL once real CI output is in The 4th finding (macOS amd64 V builds still select the static libgc.a, which this workflow's own XFAIL lane proves can't link) needs a coordinated follow-up: ship a working libgc.dylib to this branch, then update vlib/builtin/builtin_d_gcboehm.c.v to select it - in that order, since flipping the V builder first would break every current macOS amd64 tinyc+boehm build against the still-static-only binaries this branch distributes today. Tracked as a follow-up, not done in this CI-only PR. --- .github/workflows/build-and-test.yml | 93 +++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 9fdad72..3f17823 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -40,6 +40,36 @@ jobs: with: path: work/thirdparty/tcc + # Codex pullrequestreview-4780178390 on vlang/tccbin#74 (P1): the + # steps below replace this branch's checked-in tcc.exe with a + # rebuild from tinycc `mob`, and later replace libgc.a with a + # rebuild from bdwgc `master`, before ever testing either - so a + # future PR that commits a broken tcc.exe/libgc.a pair here would + # still get a passing CI result, since the suite would only ever + # exercise the unrelated freshly-rebuilt artifacts. Test the + # checked-out distribution BEFORE any rebuild step touches it. + # TEMPORARY: this branch's checked-in tcc.exe is currently known + # broken (stale v0.9.27, its lib/libc.dylib symlink targets a + # Big-Sur-era path that no longer exists on current macOS) and this + # PR does not itself update the committed binaries - so the exact + # failure signature isn't hardened yet; report the result without + # failing the job, then harden this into a signature-matched XFAIL + # (mirroring the libgc.a lane below) once the real output is known. + - name: verify the checked-in tcc.exe (as distributed, before rebuild) + working-directory: work + run: | + chmod +x thirdparty/tcc/tcc.exe + set +e + output=$(thirdparty/tccbin_tests/run.sh thirdparty/tcc/tcc.exe macos -- \ + -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ + -I thirdparty/libgc/include \ + thirdparty/tcc/lib/libgc.a \ + -ldl -lpthread 2>&1) + code=$? + set -e + echo "$output" + echo "::warning::checked-in tcc.exe/libgc.a (as distributed) result: exit=$code - see this step's comment for why this isn't blocking yet" + - name: install build dependencies # automake/libtool are needed by bdwgc's autogen.sh (aclocal, # libtoolize) for the libgc rebuild step below - not required @@ -89,14 +119,33 @@ jobs: # build_*.txt files with accurate content right after anyway. touch thirdparty/tcc/lib/build_bootstrap_marker.txt - # repo.or.cz's HTTPS endpoint is unreliable from CI (confirmed - # independently, including from a non-CI network) - git:// is - # the one that actually works (same workaround already - # verified for thirdparty-freebsd-amd64's CI). Patch the - # LOCAL copy of the official script for this run only - the + # repo.or.cz's HTTPS endpoint has been observed to fail + # intermittently in CI (confirmed independently, including from + # a non-CI network) - but unconditionally falling back to the + # unauthenticated, unencrypted git:// transport lets an on-path + # attacker feed tampered tinycc source straight into a + # compile-and-run pipeline whose output gets uploaded as a + # build artifact (Codex pullrequestreview-4780178390 on + # vlang/tccbin#74). Prefer HTTPS, retrying a few times for the + # known transient flakiness, and only fall back to git:// - + # loudly - if HTTPS is genuinely unreachable. Patch the LOCAL + # copy of the official script for this run only - the # canonical script in vlang/v is untouched. - sed -i '' 's#https://repo\.or\.cz#git://repo.or.cz#' \ - thirdparty/build_scripts/thirdparty-macos-amd64_tcc.sh + https_ok=0 + for i in 1 2 3; do + if git ls-remote https://repo.or.cz/tinycc.git >/dev/null 2>&1; then + https_ok=1 + break + fi + echo "repo.or.cz HTTPS probe failed (attempt $i/3), retrying..." + sleep 5 + done + + if [ "$https_ok" -eq 0 ]; then + echo "::warning::repo.or.cz HTTPS unreachable after 3 attempts - falling back to the unauthenticated git:// transport for this run only. Source integrity is not verified over this transport." + sed -i '' 's#https://repo\.or\.cz#git://repo.or.cz#' \ + thirdparty/build_scripts/thirdparty-macos-amd64_tcc.sh + fi bash thirdparty/build_scripts/thirdparty-macos-amd64_tcc.sh thirdparty/tcc/tcc.exe --version @@ -246,12 +295,38 @@ jobs: exit 1 fi + # The pinned run.sh (c82d3f0..., predates vlang/v#27935's + # stderr-capture fix) only ever prints "(compile error)" with no + # detail, so the summary-line match above can't tell "the known + # GC_init archive-parsing bug" apart from a DIFFERENT static-link + # regression that happens to fail the same two tests (a + # corrupted/wrong-architecture archive, an unrelated compiler + # bug) - Codex pullrequestreview-4780178390 on vlang/tccbin#74. + # Compile gc_alloc.c directly to capture the real tcc stderr and + # assert it's specifically the known unresolved-GC-symbol error, + # not just any compile failure. + direct_err=$(./thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/gc_alloc.c \ + -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ + -I ./thirdparty/libgc/include \ + ./thirdparty/tcc/lib/libgc.a \ + -ldl -lpthread \ + -o /tmp/gc_alloc_xfail_probe 2>&1) + case "$output" in *"PASS crash"*"FAIL gc_alloc (compile error)"*"FAIL hello (compile error)"*) - echo "known XFAIL: static libgc.a still can't link gc_alloc/hello, as expected - not blocking CI" + case "$direct_err" in + *"unresolved reference to '_GC_init'"*|*"unresolved reference to 'GC_init'"*) + echo "known XFAIL: static libgc.a still can't link gc_alloc/hello (unresolved GC_init, the known tcc archive-parsing bug), as expected - not blocking CI" + ;; + *) + echo "::error::libgc.a lane failed with the known summary shape, but the direct gc_alloc.c compile error does NOT mention an unresolved GC_init reference - this looks like a different, unexpected regression:" + echo "$direct_err" + exit 1 + ;; + esac ;; *) - echo "::error::Static libgc.a lane failed, but NOT with the known/expected signature (PASS crash, FAIL gc_alloc/hello with compile error) - this looks like a different, unexpected problem and should not be silently treated as the known XFAIL." + echo "::error::Static libgc.a lane failed, but NOT with the known/expected summary (PASS crash, FAIL gc_alloc/hello with compile error) - this looks like a different, unexpected problem and should not be silently treated as the known XFAIL." exit 1 ;; esac From 96bf047a750cf8ae9b571d39fb853c49428e1df8 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 19:30:18 -0400 Subject: [PATCH 17/30] ci: fix set -e aborting before the XFAIL case statement (direct_err capture needs || true, tcc's failure is expected) --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 3f17823..b285d73 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -310,7 +310,7 @@ jobs: -I ./thirdparty/libgc/include \ ./thirdparty/tcc/lib/libgc.a \ -ldl -lpthread \ - -o /tmp/gc_alloc_xfail_probe 2>&1) + -o /tmp/gc_alloc_xfail_probe 2>&1) || true case "$output" in *"PASS crash"*"FAIL gc_alloc (compile error)"*"FAIL hello (compile error)"*) From cd41c2c453f897ba8f1aa2f724e68b314658565b Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 19:32:49 -0400 Subject: [PATCH 18/30] ci: capture direct compile error for checked-in tcc.exe, to pin the exact XFAIL signature --- .github/workflows/build-and-test.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index b285d73..e33a4ce 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -68,6 +68,15 @@ jobs: code=$? set -e echo "$output" + + # First real run showed even crash.c (no GC involved) fails - + # capture the direct compiler error to pin down the exact known + # signature (expected: the broken lib/libc.dylib symlink from + # this branch's stale Big-Sur-era workaround) before hardening + # this into a real signature-matched XFAIL. + direct_err=$(thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/crash.c \ + -o /tmp/crash_checkedin_probe 2>&1) || true + echo "direct crash.c compile error on checked-in tcc.exe: $direct_err" echo "::warning::checked-in tcc.exe/libgc.a (as distributed) result: exit=$code - see this step's comment for why this isn't blocking yet" - name: install build dependencies From 1b43268972108185a7fd9b3b95ed0ef1c3d0b207 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:31:47 -0400 Subject: [PATCH 19/30] ci: fix 5 Codex findings on macos-amd64 (harden 2 XFAILs, remove insecure fallback, exact SIGSEGV check, refresh provenance) Fixes all 5 findings from pullrequestreview-4780817355 on vlang/tccbin#74: - P1: harden "verify checked-in tcc.exe" into a real signature-matched XFAIL (previously reported without validating anything at all) - asserts the known "0 passed, 3 failed" summary, confirmed via a real CI run - P1: remove the git:// fallback entirely instead of using it after HTTPS retries - an active attacker could just block HTTPS to force the fallback, so a warning-then-downgrade doesn't actually mitigate the supply-chain risk the earlier fix (pullrequestreview-4780178390) was meant to address; fail the job for real instead - P2: require exit code exactly 139 (SIGSEGV) in the no-GC fallback check, not just nonzero, so a binary that can't even start (missing loader/library) can't be mistaken for "the expected crash" - P2: validate BOTH gc_alloc.c's and hello.c's direct compile errors in the libgc.a XFAIL, not just gc_alloc.c's, so a regression isolated to hello.c alone can't hide behind the shared summary shape (same class of finding as pullrequestreview-4780815399 on the sibling freebsd-amd64 workflow, vlang/tccbin#75) - P2: overwrite the stale lib/libgc_build_cmd.txt (which described the OLD universal x86_64/arm64 build) with the actual amd64-only configure invocation this step uses, so uploaded artifacts carry accurate provenance --- .github/workflows/build-and-test.yml | 142 ++++++++++++++++++--------- 1 file changed, 97 insertions(+), 45 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index e33a4ce..1e49286 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -48,13 +48,18 @@ jobs: # still get a passing CI result, since the suite would only ever # exercise the unrelated freshly-rebuilt artifacts. Test the # checked-out distribution BEFORE any rebuild step touches it. - # TEMPORARY: this branch's checked-in tcc.exe is currently known - # broken (stale v0.9.27, its lib/libc.dylib symlink targets a - # Big-Sur-era path that no longer exists on current macOS) and this - # PR does not itself update the committed binaries - so the exact - # failure signature isn't hardened yet; report the result without - # failing the job, then harden this into a signature-matched XFAIL - # (mirroring the libgc.a lane below) once the real output is known. + # This branch's checked-in tcc.exe is currently known broken (stale + # v0.9.27, its lib/libc.dylib symlink targets a Big-Sur-era path + # that no longer exists on current macOS) and this PR does not + # itself update the committed binaries - so this is an explicit, + # signature-matched XFAIL (all 3 shared tests fail, confirmed via + # a real CI run: "0 passed, 3 failed") rather than a blocking lane, + # mirroring the libgc.a lane below: an UNEXPECTED failure shape + # (or an unexpected full pass) from a future PR that changes these + # binaries still fails the job for real instead of being silently + # absorbed - closing the gap Codex flagged where this step + # previously reported without validating anything at all + # (pullrequestreview-4780817355 on vlang/tccbin#74). - name: verify the checked-in tcc.exe (as distributed, before rebuild) working-directory: work run: | @@ -69,15 +74,21 @@ jobs: set -e echo "$output" - # First real run showed even crash.c (no GC involved) fails - - # capture the direct compiler error to pin down the exact known - # signature (expected: the broken lib/libc.dylib symlink from - # this branch's stale Big-Sur-era workaround) before hardening - # this into a real signature-matched XFAIL. - direct_err=$(thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/crash.c \ - -o /tmp/crash_checkedin_probe 2>&1) || true - echo "direct crash.c compile error on checked-in tcc.exe: $direct_err" - echo "::warning::checked-in tcc.exe/libgc.a (as distributed) result: exit=$code - see this step's comment for why this isn't blocking yet" + if [ "$code" -eq 0 ]; then + echo "::error::The checked-in tcc.exe/libgc.a now pass the conformance suite as distributed - this branch's binaries must have been fixed; remove this XFAIL special-casing (it's superseded by the rebuild-then-test lanes below anyway)." + exit 1 + fi + + case "$output" in + *"0 passed, 3 failed"*) + echo "known XFAIL: the checked-in tcc.exe/libgc.a fail all 3 shared tests (stale binary, broken libc.dylib symlink), as expected - not blocking CI" + ;; + *) + echo "::error::The checked-in tcc.exe/libgc.a failed, but NOT with the known/expected summary (0 passed, 3 failed) - this looks like a different, unexpected regression in the binaries this PR proposes to commit:" + echo "$output" + exit 1 + ;; + esac - name: install build dependencies # automake/libtool are needed by bdwgc's autogen.sh (aclocal, @@ -130,16 +141,25 @@ jobs: # repo.or.cz's HTTPS endpoint has been observed to fail # intermittently in CI (confirmed independently, including from - # a non-CI network) - but unconditionally falling back to the - # unauthenticated, unencrypted git:// transport lets an on-path - # attacker feed tampered tinycc source straight into a + # a non-CI network). An earlier version of this step fell back + # to the unauthenticated, unencrypted git:// transport when + # HTTPS failed - but that fallback is itself the vulnerability: + # an active on-path attacker can simply block HTTPS to FORCE + # the fallback, then feed tampered tinycc source into a # compile-and-run pipeline whose output gets uploaded as a - # build artifact (Codex pullrequestreview-4780178390 on - # vlang/tccbin#74). Prefer HTTPS, retrying a few times for the - # known transient flakiness, and only fall back to git:// - - # loudly - if HTTPS is genuinely unreachable. Patch the LOCAL - # copy of the official script for this run only - the - # canonical script in vlang/v is untouched. + # build artifact. A warning in the log doesn't mitigate that - + # the tampered source still gets built and run either way + # (Codex pullrequestreview-4780817355 on vlang/tccbin#74, + # tightening pullrequestreview-4780178390's earlier retry-then- + # fall-back mitigation). Retry HTTPS for the known transient + # flakiness, but if it's still unreachable, fail the job for + # real rather than downgrading transport - an occasional CI + # failure is a far smaller cost than a supply-chain compromise + # vector, and tinycc has no alternative TLS-backed mirror to + # fall back to (repo.or.cz is canonical, no GitHub mirror + # exists) or trusted fixed hash to verify against (this step + # intentionally tracks the moving `mob` tip, not a pinned + # commit). https_ok=0 for i in 1 2 3; do if git ls-remote https://repo.or.cz/tinycc.git >/dev/null 2>&1; then @@ -151,9 +171,8 @@ jobs: done if [ "$https_ok" -eq 0 ]; then - echo "::warning::repo.or.cz HTTPS unreachable after 3 attempts - falling back to the unauthenticated git:// transport for this run only. Source integrity is not verified over this transport." - sed -i '' 's#https://repo\.or\.cz#git://repo.or.cz#' \ - thirdparty/build_scripts/thirdparty-macos-amd64_tcc.sh + echo "::error::repo.or.cz HTTPS unreachable after 3 attempts - failing rather than falling back to the unauthenticated git:// transport (see this step's comment). Re-run the job; if this persists, investigate repo.or.cz's HTTPS availability directly." + exit 1 fi bash thirdparty/build_scripts/thirdparty-macos-amd64_tcc.sh @@ -221,6 +240,16 @@ jobs: date > "$TCC_FOLDER/lib/libgc_build_on_date.txt" echo "$LIBGC_COMMIT_FULL_HASH" > "$TCC_FOLDER/lib/libgc_build_source_hash.txt" uname -a > "$TCC_FOLDER/lib/libgc_build_machine_uname.txt" + # The checked-in lib/libgc_build_cmd.txt describes the OLD + # archive's build (a universal x86_64/arm64 configure + # invocation) - if left untouched it would keep claiming that + # for the archive THIS step just produced with a materially + # different, amd64-only configure line, making every uploaded + # artifact's provenance false (Codex pullrequestreview- + # 4780817355 on vlang/tccbin#74). Overwrite it with the actual + # command used above. + echo "CC=\"$CC\" CFLAGS=\"-Os -mtune=generic -fPIC\" LDFLAGS=\"-Os -fPIC\" ./configure --disable-dependency-tracking --disable-docs --enable-static=yes --enable-shared=yes --enable-single-obj-compilation --enable-gc-debug --enable-thread-local-alloc --enable-large-config --enable-cplusplus --with-libatomic-ops=check --enable-sigrt-signals" \ + > "$TCC_FOLDER/lib/libgc_build_cmd.txt" rsync -a bdwgc/.libs/ "$TCC_FOLDER/lib/" ls -la "$TCC_FOLDER/lib/" @@ -273,11 +302,19 @@ jobs: /tmp/crash_nogc.exe code=$? set -e - if [ "$code" -eq 0 ]; then - echo "expected a nonzero exit from an unguarded null-pointer dereference, got 0" >&2 + # Any nonzero exit isn't enough: if the executable can't even + # start (missing loader, an unresolved dynamic library) the + # shell also reports a nonzero status, which this check would + # wrongly accept as "the expected crash" (Codex + # pullrequestreview-4780817355 on vlang/tccbin#74). crash.c's + # null-pointer dereference is expected to be killed by SIGSEGV + # specifically, which a POSIX shell reports as exit code + # 128+11=139 - verify that exact signal-terminated signature. + if [ "$code" -ne 139 ]; then + echo "expected exit 139 (killed by SIGSEGV) from an unguarded null-pointer dereference, got $code" >&2 exit 1 fi - echo "no-GC compile+crash check passed (exit=$code)" + echo "no-GC compile+crash check passed (killed by SIGSEGV as expected)" # tcc cannot yet link this freshly-rebuilt static libgc.a at all # (see the comment above the dylib lane) - this is a temporary, @@ -311,28 +348,43 @@ jobs: # regression that happens to fail the same two tests (a # corrupted/wrong-architecture archive, an unrelated compiler # bug) - Codex pullrequestreview-4780178390 on vlang/tccbin#74. - # Compile gc_alloc.c directly to capture the real tcc stderr and - # assert it's specifically the known unresolved-GC-symbol error, - # not just any compile failure. - direct_err=$(./thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/gc_alloc.c \ + # Compile BOTH failing tests directly to capture their real tcc + # stderr and assert each is specifically the known unresolved- + # GC-symbol error - checking only one of the two (e.g. + # gc_alloc.c) would let an unrelated regression isolated to the + # OTHER test (hello.c) hide behind this same summary shape and + # pass unnoticed (same class of gap as Codex pullrequestreview- + # 4780815399 on the sibling freebsd-amd64 workflow, vlang/tccbin#75). + direct_err_gc_alloc=$(./thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/gc_alloc.c \ -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ -I ./thirdparty/libgc/include \ ./thirdparty/tcc/lib/libgc.a \ -ldl -lpthread \ -o /tmp/gc_alloc_xfail_probe 2>&1) || true + direct_err_hello=$(./thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/hello.c \ + -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ + -I ./thirdparty/libgc/include \ + ./thirdparty/tcc/lib/libgc.a \ + -ldl -lpthread \ + -o /tmp/hello_xfail_probe 2>&1) || true + + is_known_gc_init() { + case "$1" in + *"unresolved reference to '_GC_init'"*|*"unresolved reference to 'GC_init'"*) return 0 ;; + *) return 1 ;; + esac + } case "$output" in *"PASS crash"*"FAIL gc_alloc (compile error)"*"FAIL hello (compile error)"*) - case "$direct_err" in - *"unresolved reference to '_GC_init'"*|*"unresolved reference to 'GC_init'"*) - echo "known XFAIL: static libgc.a still can't link gc_alloc/hello (unresolved GC_init, the known tcc archive-parsing bug), as expected - not blocking CI" - ;; - *) - echo "::error::libgc.a lane failed with the known summary shape, but the direct gc_alloc.c compile error does NOT mention an unresolved GC_init reference - this looks like a different, unexpected regression:" - echo "$direct_err" - exit 1 - ;; - esac + if is_known_gc_init "$direct_err_gc_alloc" && is_known_gc_init "$direct_err_hello"; then + echo "known XFAIL: static libgc.a still can't link gc_alloc.c/hello.c (unresolved GC_init, the known tcc archive-parsing bug), as expected - not blocking CI" + else + echo "::error::libgc.a lane failed with the known summary shape, but at least one of gc_alloc.c/hello.c's direct compile errors does NOT mention an unresolved GC_init reference - this looks like a different, unexpected regression:" + echo "gc_alloc.c: $direct_err_gc_alloc" + echo "hello.c: $direct_err_hello" + exit 1 + fi ;; *) echo "::error::Static libgc.a lane failed, but NOT with the known/expected summary (PASS crash, FAIL gc_alloc/hello with compile error) - this looks like a different, unexpected problem and should not be silently treated as the known XFAIL." From 3092060bdba948b51af18f87e279278e9c845a60 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sun, 26 Jul 2026 06:15:48 -0400 Subject: [PATCH 20/30] ci: fix 5 more Codex findings on macos-amd64 (harden checked-in XFAIL, warn on packaged libgc.a, pin libatomic_ops provenance, deployment target, exclusive symbol match) Fixes all 5 findings from pullrequestreview-4780918023 on vlang/tccbin#74: - P1: harden the checked-in-tcc.exe XFAIL beyond the aggregate "0 passed, 3 failed" count - now compiles crash.c directly and requires the known "library 'c' not found" signature (the broken libc.dylib symlink already root-caused earlier this session) - P1: warn against publishing the unlinkable static libgc.a as a ready distribution - adds a KNOWN_ISSUES.txt into the packaged artifact explaining the dylib is the verified-working link path - P2: record libatomic_ops's resolved commit alongside bdwgc's in provenance (it was cloned from a moving branch but untracked) - P1: pin MACOSX_DEPLOYMENT_TARGET=10.13 for both the tcc and libgc rebuilds, and surface the checked-in binary's actual load-command minimum via otool for future refinement - without it, clang stamps rebuilt binaries with the CI runner's own macOS 15 minimum - P2: the libgc.a XFAIL's GC_init check now also rejects any unresolved-reference symbol that isn't GC_-prefixed, not just requiring GC_init's presence - closing the same class of gap as pullrequestreview-4780907186 on the sibling freebsd-amd64 workflow --- .github/workflows/build-and-test.yml | 116 +++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 1e49286..d3d303d 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -64,6 +64,20 @@ jobs: working-directory: work run: | chmod +x thirdparty/tcc/tcc.exe + + # Informational: what deployment target does the CURRENTLY + # distributed binary actually support? Neither this workflow's + # rebuild nor the official upstream build script sets + # MACOSX_DEPLOYMENT_TARGET, so without one, clang stamps + # rebuilt binaries with the CI runner's own macOS 15 minimum - + # silently dropping support for whatever older macOS versions + # this checked-in binary was actually built to support (Codex + # pullrequestreview-4780918023 on vlang/tccbin#74). Surface the + # old binary's real minimum here, before it's overwritten, so + # the fixed target below can be refined against real data + # rather than a guess. + otool -l thirdparty/tcc/tcc.exe | grep -A3 "LC_VERSION_MIN_MACOSX\|LC_BUILD_VERSION" || echo "(no LC_VERSION_MIN_MACOSX/LC_BUILD_VERSION load command found)" + set +e output=$(thirdparty/tccbin_tests/run.sh thirdparty/tcc/tcc.exe macos -- \ -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ @@ -79,9 +93,32 @@ jobs: exit 1 fi + # "0 passed, 3 failed" is only an aggregate count - a future PR + # could replace tcc.exe/libgc.a with a DIFFERENTLY broken pair + # (corrupt archive, wrong architecture) and still produce this + # same count, passing CI unnoticed (Codex pullrequestreview- + # 4780918023 on vlang/tccbin#74). Compile crash.c directly + # (no GC involved, so any failure here is specifically about + # this binary's basic linking, not a GC-archive issue) and + # assert it's specifically the known broken-libc.dylib-symlink + # error already root-caused earlier this session: tcc reports + # "library 'c' not found" when its lib/libc.dylib symlink + # target doesn't exist. + direct_err_checkedin=$(thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/crash.c \ + -o /tmp/crash_checkedin_probe 2>&1) || true + case "$output" in *"0 passed, 3 failed"*) - echo "known XFAIL: the checked-in tcc.exe/libgc.a fail all 3 shared tests (stale binary, broken libc.dylib symlink), as expected - not blocking CI" + case "$direct_err_checkedin" in + *"library 'c' not found"*) + echo "known XFAIL: the checked-in tcc.exe/libgc.a fail all 3 shared tests (stale binary, broken libc.dylib symlink - library 'c' not found), as expected - not blocking CI" + ;; + *) + echo "::error::The checked-in tcc.exe/libgc.a failed with the known aggregate count, but the direct crash.c compile error does NOT mention the known broken-libc.dylib-symlink signature - this looks like a different, unexpected regression:" + echo "$direct_err_checkedin" + exit 1 + ;; + esac ;; *) echo "::error::The checked-in tcc.exe/libgc.a failed, but NOT with the known/expected summary (0 passed, 3 failed) - this looks like a different, unexpected regression in the binaries this PR proposes to commit:" @@ -118,12 +155,23 @@ jobs: # is the actual fix, reusing this branch's already-committed # libgc.a (the script's own rsync step preserves it, no GC # rebuild needed). + # MACOSX_DEPLOYMENT_TARGET pinned to 10.13 (High Sierra, 2017) - + # without it, clang stamps rebuilt binaries with this CI runner's + # own macOS 15 minimum, so the uploaded generic macOS-amd64 + # distribution silently couldn't run on older Intel macOS releases + # the existing prebuilt distribution supports (Codex + # pullrequestreview-4780918023 on vlang/tccbin#74). 10.13 is a + # conservative, widely-used baseline for Intel-only distributions; + # the "verify the checked-in tcc.exe" step above now also surfaces + # the CURRENT binary's actual load-command minimum via otool, to + # refine this value against real data rather than a guess. - name: build tcc.exe from current tinycc (mob) working-directory: work env: TCC_COMMIT: mob TCC_FOLDER: thirdparty/tcc CC: clang + MACOSX_DEPLOYMENT_TARGET: "10.13" run: | set -eu # The official script's own carry-forward step @@ -209,6 +257,10 @@ jobs: CC: clang TCC_FOLDER: thirdparty/tcc LIBGC_COMMIT: master + # See the "build tcc.exe" step's comment - same deployment- + # target gap Codex flagged applies to this build too (Codex + # pullrequestreview-4780918023 on vlang/tccbin#74). + MACOSX_DEPLOYMENT_TARGET: "10.13" run: | set -eu rm -rf bdwgc/ @@ -216,7 +268,15 @@ jobs: cd bdwgc/ git checkout --quiet "$LIBGC_COMMIT" LIBGC_COMMIT_FULL_HASH="$(git rev-parse HEAD)" + # libatomic_ops is cloned from its moving default branch and + # gets compiled into the resulting archive, but the provenance + # below previously recorded only bdwgc's own commit - two + # artifacts could carry an identical libgc_build_source_hash.txt + # while containing different libatomic_ops code, making the + # archive untraceable (Codex pullrequestreview-4780918023 on + # vlang/tccbin#74). Record its resolved commit too. git clone --quiet https://github.com/bdwgc/libatomic_ops + LIBATOMIC_OPS_COMMIT_FULL_HASH="$(git -C libatomic_ops rev-parse HEAD)" ./autogen.sh CC="$CC" CFLAGS="-Os -mtune=generic -fPIC" LDFLAGS="-Os -fPIC" ./configure \ --disable-dependency-tracking \ @@ -237,9 +297,10 @@ jobs: otool -D "$dname" done cd ../.. - date > "$TCC_FOLDER/lib/libgc_build_on_date.txt" - echo "$LIBGC_COMMIT_FULL_HASH" > "$TCC_FOLDER/lib/libgc_build_source_hash.txt" - uname -a > "$TCC_FOLDER/lib/libgc_build_machine_uname.txt" + date > "$TCC_FOLDER/lib/libgc_build_on_date.txt" + echo "$LIBGC_COMMIT_FULL_HASH" > "$TCC_FOLDER/lib/libgc_build_source_hash.txt" + echo "$LIBATOMIC_OPS_COMMIT_FULL_HASH" > "$TCC_FOLDER/lib/libgc_build_libatomic_ops_source_hash.txt" + uname -a > "$TCC_FOLDER/lib/libgc_build_machine_uname.txt" # The checked-in lib/libgc_build_cmd.txt describes the OLD # archive's build (a universal x86_64/arm64 configure # invocation) - if left untouched it would keep claiming that @@ -368,11 +429,25 @@ jobs: -ldl -lpthread \ -o /tmp/hello_xfail_probe 2>&1) || true + # The known bug is that tcc can't parse this WHOLE archive, so + # every GC_* symbol the test references legitimately shows up + # as unresolved together (gc_alloc.c: _GC_init, _GC_malloc, + # _GC_noop1; hello.c: _GC_init, _GC_noop1) - checking for one + # exact symbol name alone isn't enough to also catch a + # DIFFERENT regression that adds a non-GC unresolved symbol + # alongside the known ones (Codex pullrequestreview-4780907186 + # on the sibling freebsd-amd64 workflow, vlang/tccbin#75, same + # class of gap). Require GC_init specifically to confirm the + # known root cause, AND reject if any unresolved-reference line + # does NOT match the GC_-prefixed pattern the archive-parsing + # bug produces. is_known_gc_init() { case "$1" in - *"unresolved reference to '_GC_init'"*|*"unresolved reference to 'GC_init'"*) return 0 ;; + *"unresolved reference to '_GC_init'"*|*"unresolved reference to 'GC_init'"*) ;; *) return 1 ;; esac + other=$(printf '%s\n' "$1" | grep -oE "unresolved reference to '[^']+'" | grep -v -E "unresolved reference to '_?GC_[A-Za-z0-9_]+'") + [ -z "$other" ] } case "$output" in @@ -392,10 +467,39 @@ jobs: ;; esac + # This artifact bundles thirdparty/tcc/lib/libgc.a even though the + # XFAIL lane above proves this tcc.exe can't link it - V's existing + # tinyc+boehm path for macOS amd64 still selects that static + # archive (vlib/builtin/builtin_d_gcboehm.c.v), so anyone who + # downloads and installs this as the distribution would hit + # working GC builds turning into silent link failures, despite the + # dynamic dylib lane passing (Codex pullrequestreview-4780918023 + # on vlang/tccbin#74). Not pulling libgc.a out of the archive - + # some future consumer may still want it for reference/debugging, + # and V's builder isn't switched to the dylib yet (see this PR's + # earlier reply on that sequencing) - but a downloader must not be + # able to miss this caveat. - name: package rebuilt tcc for upload if: ${{ !cancelled() }} working-directory: work - run: tar --exclude=.git -czf tccbin-macos-amd64-current.tar.gz thirdparty/tcc + run: | + cat > thirdparty/tcc/KNOWN_ISSUES.txt <<'EOF' + This is a CI-verification build, not a drop-in replacement for the + committed tccbin-macos-amd64 distribution. + + lib/libgc.a (static archive) CANNOT be linked by this tcc.exe - + confirmed via a real CI run: tcc reports "unresolved reference to + '_GC_init'" etc. even though the symbol is present and well-formed + in the archive (a tcc Mach-O archive-parsing limitation, the same + one already documented on macos-arm64). Use lib/libgc.dylib with + -Wl,-rpath, instead; that link path is verified + working in this build's own CI run. + + V's existing builtin_d_gcboehm.c.v for macOS amd64+tinyc still + selects the static libgc.a - do not switch it to the dylib until a + build like this one is committed to the actual distribution branch. + EOF + tar --exclude=.git -czf tccbin-macos-amd64-current.tar.gz thirdparty/tcc - name: upload rebuilt binaries if: ${{ !cancelled() }} From 0d39b09c1b0429f1daf59397e7baaec3e3344902 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sun, 26 Jul 2026 07:36:35 -0400 Subject: [PATCH 21/30] ci: fix 4 more Codex findings on macos-amd64 (gate artifact claims on real outcomes, positive no-GC check, broader exclusivity check, deployment target in provenance) Fixes 2 findings from pullrequestreview-4781470066 on vlang/tccbin#74: - P2: package/upload steps stay `!cancelled()` (still publish a debug artifact on failure, matching macos-arm64's own rationale), but the bundled KNOWN_ISSUES.txt's claim that the dylib link path is "verified working" is now conditional on the dylib_test/nogc_test steps' actual outcomes, read via new step `id`s - verified against both success and failure outcome combinations - P2: lib/libgc_build_cmd.txt now records MACOSX_DEPLOYMENT_TARGET alongside the configure invocation, so reproducing this build from the recorded command actually matches its compatibility promise Fixes 2 findings from pullrequestreview-4781468264 on the sibling freebsd-amd64 workflow, vlang/tccbin#75, same class of gap present here: - P2: the libgc.a XFAIL's exclusivity check now matches on tcc's `tcc: error:` line prefix instead of only "unresolved reference to" lines, so a wholly different KIND of tcc error (not just a differently-named symbol) riding alongside the known GC_init error can no longer hide behind the check - verified empirically: the old narrower check accepted a mocked known-symbol-plus-unrelated-error case, the new one correctly rejects it - P2: added a positive no-GC check (compile+run a trivial non-crashing program, require exit 0) alongside the existing crash.c-must- SIGSEGV check, so a broken startup path that crashes EVERY program can't hide behind "the crash happened as expected" - verified empirically against a mocked always-crashing tcc: old logic would have passed, new logic correctly fails --- .github/workflows/build-and-test.yml | 80 +++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index d3d303d..e3d43cc 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -308,8 +308,12 @@ jobs: # different, amd64-only configure line, making every uploaded # artifact's provenance false (Codex pullrequestreview- # 4780817355 on vlang/tccbin#74). Overwrite it with the actual - # command used above. - echo "CC=\"$CC\" CFLAGS=\"-Os -mtune=generic -fPIC\" LDFLAGS=\"-Os -fPIC\" ./configure --disable-dependency-tracking --disable-docs --enable-static=yes --enable-shared=yes --enable-single-obj-compilation --enable-gc-debug --enable-thread-local-alloc --enable-large-config --enable-cplusplus --with-libatomic-ops=check --enable-sigrt-signals" \ + # command used above - including MACOSX_DEPLOYMENT_TARGET, + # which materially affects dylib/archive compatibility and + # would otherwise be missing from anyone trying to reproduce + # this exact build from the recorded command (Codex + # pullrequestreview-4781470066 on vlang/tccbin#74). + echo "MACOSX_DEPLOYMENT_TARGET=\"$MACOSX_DEPLOYMENT_TARGET\" CC=\"$CC\" CFLAGS=\"-Os -mtune=generic -fPIC\" LDFLAGS=\"-Os -fPIC\" ./configure --disable-dependency-tracking --disable-docs --enable-static=yes --enable-shared=yes --enable-single-obj-compilation --enable-gc-debug --enable-thread-local-alloc --enable-large-config --enable-cplusplus --with-libatomic-ops=check --enable-sigrt-signals" \ > "$TCC_FOLDER/lib/libgc_build_cmd.txt" rsync -a bdwgc/.libs/ "$TCC_FOLDER/lib/" ls -la "$TCC_FOLDER/lib/" @@ -333,6 +337,7 @@ jobs: # and keep the static libgc.a path as an explicit, signature-checked # XFAIL rather than silently dropping it. - name: run shared conformance tests (libgc.dylib, dynamic - blocking) + id: dylib_test working-directory: work run: | chmod +x thirdparty/tccbin_tests/run.sh @@ -351,6 +356,7 @@ jobs: # link line whatsoever. Check that directly, mirroring macos-arm64's # own equivalent step. - name: verify no-GC fallback path (plain tcc, no libgc linked) + id: nogc_test if: ${{ !cancelled() }} working-directory: work run: | @@ -377,6 +383,37 @@ jobs: fi echo "no-GC compile+crash check passed (killed by SIGSEGV as expected)" + # crash.c exiting 139 only proves SOME crash happened - if a + # broken libtcc1.a/crt or startup path made EVERY program + # segfault immediately (before ever reaching the intentional + # null-pointer dereference), this check would still see exit + # 139 and wrongly call it "expected" (Codex pullrequestreview- + # 4781468264 on the sibling freebsd-amd64 workflow, vlang/ + # tccbin#75, same class of gap here). The GC-backed lanes + # can't provide this positive check either, since the static + # one is expected to fail at link time. Compile and run a + # trivial, non-crashing program with the same no-GC flags, + # requiring it to actually complete and exit 0 - proving the + # toolchain can still produce a genuinely working binary, not + # just one that crashes regardless of source. + cat > /tmp/nogc_trivial.c <<'TRIVIALEOF' + int main(void) { return 0; } + TRIVIALEOF + thirdparty/tcc/tcc.exe /tmp/nogc_trivial.c -o /tmp/nogc_trivial.exe + if [ ! -x /tmp/nogc_trivial.exe ]; then + echo "expected tcc to produce a runnable trivial no-GC binary, but it's missing or not executable" >&2 + exit 1 + fi + set +e + /tmp/nogc_trivial.exe + trivial_code=$? + set -e + if [ "$trivial_code" -ne 0 ]; then + echo "expected the trivial no-GC program to exit 0, got $trivial_code - the no-GC toolchain path may be broken (e.g. crashing at startup) even though the crash.c check above happened to pass" >&2 + exit 1 + fi + echo "trivial no-GC success check passed (exit=0)" + # tcc cannot yet link this freshly-rebuilt static libgc.a at all # (see the comment above the dylib lane) - this is a temporary, # explicit XFAIL, not a blanket continue-on-error, so an unexpected @@ -446,7 +483,19 @@ jobs: *"unresolved reference to '_GC_init'"*|*"unresolved reference to 'GC_init'"*) ;; *) return 1 ;; esac - other=$(printf '%s\n' "$1" | grep -oE "unresolved reference to '[^']+'" | grep -v -E "unresolved reference to '_?GC_[A-Za-z0-9_]+'") + # Filtering only "unresolved reference to '...'" lines and + # checking their NAMES misses a wholly DIFFERENT kind of tcc + # error (invalid object, relocation error, etc.) riding + # alongside the known one - such a line simply wouldn't match + # the "unresolved reference to" pattern at extraction, so + # `other` would stay empty and this would wrongly accept the + # XFAIL (Codex pullrequestreview-4781468264 on the sibling + # freebsd-amd64 workflow, vlang/tccbin#75, same class of gap + # here). Match on tcc's actual error-line prefix instead, so + # ANY tcc error line that isn't specifically a GC_-prefixed + # unresolved reference gets caught, not just a differently- + # named one. + other=$(printf '%s\n' "$1" | grep -E '^tcc: error:' | grep -v -E "unresolved reference to '_?GC_[A-Za-z0-9_]+'") [ -z "$other" ] } @@ -479,11 +528,30 @@ jobs: # and V's builder isn't switched to the dylib yet (see this PR's # earlier reply on that sequencing) - but a downloader must not be # able to miss this caveat. + # `!cancelled()` stays true after an ORDINARY step failure (it only + # excludes a cancelled run), so a failure in the blocking dylib + # lane or the no-GC check still reached this step - publishing an + # artifact whose bundled KNOWN_ISSUES.txt unconditionally claimed + # "verified working in this build's own CI run" even when this + # run's own validation didn't pass (Codex pullrequestreview- + # 4781470066 on vlang/tccbin#74). Keep publishing on failure too - + # a maintainer debugging a red run still gets a downloadable build + # to reproduce against (mirroring macos-arm64's own rationale for + # `!cancelled()` here) - but make the claim match what THIS run + # actually verified, using the two prior steps' real outcomes. - name: package rebuilt tcc for upload if: ${{ !cancelled() }} working-directory: work + env: + DYLIB_TEST_OUTCOME: ${{ steps.dylib_test.outcome }} + NOGC_TEST_OUTCOME: ${{ steps.nogc_test.outcome }} run: | - cat > thirdparty/tcc/KNOWN_ISSUES.txt <<'EOF' + if [ "$DYLIB_TEST_OUTCOME" = "success" ] && [ "$NOGC_TEST_OUTCOME" = "success" ]; then + dylib_claim="Use lib/libgc.dylib with -Wl,-rpath, instead; that link path is verified working in this build's own CI run." + else + dylib_claim="THIS BUILD'S OWN CI VALIDATION DID NOT PASS (dylib test: $DYLIB_TEST_OUTCOME, no-GC test: $NOGC_TEST_OUTCOME) - do not treat lib/libgc.dylib as a verified-working link path from this specific artifact. Check the workflow run's log before relying on it." + fi + cat > thirdparty/tcc/KNOWN_ISSUES.txt < instead; that link path is verified - working in this build's own CI run. + one already documented on macos-arm64). $dylib_claim V's existing builtin_d_gcboehm.c.v for macOS amd64+tinyc still selects the static libgc.a - do not switch it to the dylib until a From af46fa6aa7a926cb84418630fd31a1fc6f12ae26 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sun, 26 Jul 2026 13:01:55 -0400 Subject: [PATCH 22/30] ci: retry the whole tcc build script on network failure; preserve XFAIL probe exit codes Fixes a real CI failure (run 30200458603): the HTTPS-reachability probe (git ls-remote) succeeded on retry, but the official build script's own git clone then failed anyway - "Failed to connect to repo.or.cz port 443... Couldn't connect to server" - and with the insecure git:// fallback already removed for security reasons, there was nothing left to recover with. A lightweight probe succeeding doesn't guarantee the much heavier clone moments later will also succeed. Now retries the ACTUAL build script invocation (not a decoupled proxy check) up to 3 times, but only when the failure specifically matches a network-connection signature (Failed to connect, Couldn't connect to server, Could not resolve host, Connection reset by peer, Recv failure, Connection timed out) - a genuine build bug (a real gmake or configure error) still fails fast instead of wasting 3 attempts on something retrying can't fix. Safe to retry: the script itself does `rm -rf tinycc/` unconditionally as its first action, and the network operation happens before any step that mutates $TCC_FOLDER, so no partial state survives across attempts. Verified against 3 mocked scenarios: network-failure-then-success, unrelated-bug-fails-fast, and network-failure-exhausts-all-retries. Also fixes 2 findings from pullrequestreview-4781865117 on the sibling freebsd-amd64 workflow, vlang/tccbin#75 (same class of gap, applied proactively here): the libgc.a XFAIL's direct-error probes and the checked-in-binary probe used `|| true`, discarding the probe's own exit code entirely - a regression that made tcc print the expected error text and then crash/abort (SIGSEGV, SIGABRT) would still pass. Now captures the real exit code alongside the text and rejects any signal-terminated exit (128+signal) even when the expected text is present. Verified empirically: a mocked "expected text + SIGSEGV exit" case passed under the old logic, and is correctly rejected now. --- .github/workflows/build-and-test.yml | 111 ++++++++++++++++++++------- 1 file changed, 82 insertions(+), 29 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index e3d43cc..1ad0a38 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -104,11 +104,26 @@ jobs: # error already root-caused earlier this session: tcc reports # "library 'c' not found" when its lib/libc.dylib symlink # target doesn't exist. + set +e direct_err_checkedin=$(thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/crash.c \ - -o /tmp/crash_checkedin_probe 2>&1) || true + -o /tmp/crash_checkedin_probe 2>&1) + direct_code_checkedin=$? + set -e case "$output" in *"0 passed, 3 failed"*) + # A regression could make tcc print the expected "library + # 'c' not found" diagnostic and then crash/abort rather + # than exiting cleanly - the earlier `|| true` discarded + # the exit code entirely (same class of gap Codex flagged + # in pullrequestreview-4781865117 on vlang/tccbin#75 for + # the libgc.a XFAIL probes). Reject a signal-terminated + # exit (128+signal) even if the expected text is present. + if [ "$direct_code_checkedin" -gt 128 ]; then + echo "::error::The checked-in tcc.exe/libgc.a failed with the known aggregate count, but the direct crash.c probe exited abnormally (exit=$direct_code_checkedin, signal-terminated) rather than tcc's normal compile-error status - this looks like a different, unexpected regression:" + echo "$direct_err_checkedin" + exit 1 + fi case "$direct_err_checkedin" in *"library 'c' not found"*) echo "known XFAIL: the checked-in tcc.exe/libgc.a fail all 3 shared tests (stale binary, broken libc.dylib symlink - library 'c' not found), as expected - not blocking CI" @@ -199,31 +214,52 @@ jobs: # the tampered source still gets built and run either way # (Codex pullrequestreview-4780817355 on vlang/tccbin#74, # tightening pullrequestreview-4780178390's earlier retry-then- - # fall-back mitigation). Retry HTTPS for the known transient - # flakiness, but if it's still unreachable, fail the job for - # real rather than downgrading transport - an occasional CI - # failure is a far smaller cost than a supply-chain compromise - # vector, and tinycc has no alternative TLS-backed mirror to - # fall back to (repo.or.cz is canonical, no GitHub mirror - # exists) or trusted fixed hash to verify against (this step - # intentionally tracks the moving `mob` tip, not a pinned - # commit). - https_ok=0 - for i in 1 2 3; do - if git ls-remote https://repo.or.cz/tinycc.git >/dev/null 2>&1; then - https_ok=1 + # fall-back mitigation). No downgrade path exists anymore - if + # HTTPS is genuinely unreachable, the job fails for real. + # + # A lightweight `git ls-remote` probe succeeding beforehand + # does NOT guarantee the heavier `git clone` the official + # script performs moments later will also succeed - confirmed + # by a real CI failure (run 30200458603): the probe passed on + # retry, but the script's own clone then failed anyway with + # "Failed to connect to repo.or.cz port 443... Couldn't + # connect to server", and with the fallback removed there was + # nothing left to recover with. Retry the ACTUAL build script + # invocation instead of a decoupled proxy check - but only + # when its failure specifically matches a network-connection + # signature, so a genuine build bug (a real gmake failure, a + # configure error) fails fast instead of wasting 3 attempts on + # something retrying will never fix. The script itself does + # `rm -rf tinycc/` unconditionally as its first action, and + # the network operation (git clone) happens before any step + # that mutates $TCC_FOLDER, so retrying the whole script after + # a network failure is safe - no partial state to clean up. + attempt=0 + while true; do + attempt=$((attempt + 1)) + set +e + build_output=$(bash thirdparty/build_scripts/thirdparty-macos-amd64_tcc.sh 2>&1) + build_code=$? + set -e + echo "$build_output" + if [ "$build_code" -eq 0 ]; then break fi - echo "repo.or.cz HTTPS probe failed (attempt $i/3), retrying..." - sleep 5 + case "$build_output" in + *"Failed to connect"*|*"Couldn't connect to server"*|*"Could not resolve host"*|*"Connection reset by peer"*|*"Recv failure"*|*"Connection timed out"*) + if [ "$attempt" -ge 3 ]; then + echo "::error::repo.or.cz remained unreachable after $attempt attempts (network-connection failure) - failing rather than falling back to the unauthenticated git:// transport. Re-run the job; if this persists, investigate repo.or.cz's HTTPS availability directly." + exit 1 + fi + echo "tinycc build attempt $attempt failed due to a network-connection error reaching repo.or.cz - retrying ($attempt/3)..." + sleep 10 + ;; + *) + echo "::error::tinycc build failed for a reason unrelated to repo.or.cz connectivity - not retrying, since retrying wouldn't fix a real build error." + exit 1 + ;; + esac done - - if [ "$https_ok" -eq 0 ]; then - echo "::error::repo.or.cz HTTPS unreachable after 3 attempts - failing rather than falling back to the unauthenticated git:// transport (see this step's comment). Re-run the job; if this persists, investigate repo.or.cz's HTTPS availability directly." - exit 1 - fi - - bash thirdparty/build_scripts/thirdparty-macos-amd64_tcc.sh thirdparty/tcc/tcc.exe --version thirdparty/tcc/tcc.exe -v -v @@ -453,18 +489,22 @@ jobs: # OTHER test (hello.c) hide behind this same summary shape and # pass unnoticed (same class of gap as Codex pullrequestreview- # 4780815399 on the sibling freebsd-amd64 workflow, vlang/tccbin#75). + set +e direct_err_gc_alloc=$(./thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/gc_alloc.c \ -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ -I ./thirdparty/libgc/include \ ./thirdparty/tcc/lib/libgc.a \ -ldl -lpthread \ - -o /tmp/gc_alloc_xfail_probe 2>&1) || true + -o /tmp/gc_alloc_xfail_probe 2>&1) + direct_code_gc_alloc=$? direct_err_hello=$(./thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/hello.c \ -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ -I ./thirdparty/libgc/include \ ./thirdparty/tcc/lib/libgc.a \ -ldl -lpthread \ - -o /tmp/hello_xfail_probe 2>&1) || true + -o /tmp/hello_xfail_probe 2>&1) + direct_code_hello=$? + set -e # The known bug is that tcc can't parse this WHOLE archive, so # every GC_* symbol the test references legitimately shows up @@ -479,6 +519,19 @@ jobs: # does NOT match the GC_-prefixed pattern the archive-parsing # bug produces. is_known_gc_init() { + # $1 = captured stderr text, $2 = the probe's own exit code. + # A regression could make tcc print the expected GC_init + # diagnostic and then crash/abort rather than exiting + # cleanly with its normal compile-error status - the earlier + # `|| true` discarded that exit code entirely (Codex + # pullrequestreview-4781865117 on the sibling freebsd-amd64 + # workflow, vlang/tccbin#75, same class of gap here). A + # POSIX shell reports a signal-terminated process as exit + # code 128+signal; reject those instead of treating any + # nonzero exit as "the expected compile-error status". + if [ "$2" -gt 128 ]; then + return 1 + fi case "$1" in *"unresolved reference to '_GC_init'"*|*"unresolved reference to 'GC_init'"*) ;; *) return 1 ;; @@ -501,12 +554,12 @@ jobs: case "$output" in *"PASS crash"*"FAIL gc_alloc (compile error)"*"FAIL hello (compile error)"*) - if is_known_gc_init "$direct_err_gc_alloc" && is_known_gc_init "$direct_err_hello"; then + if is_known_gc_init "$direct_err_gc_alloc" "$direct_code_gc_alloc" && is_known_gc_init "$direct_err_hello" "$direct_code_hello"; then echo "known XFAIL: static libgc.a still can't link gc_alloc.c/hello.c (unresolved GC_init, the known tcc archive-parsing bug), as expected - not blocking CI" else - echo "::error::libgc.a lane failed with the known summary shape, but at least one of gc_alloc.c/hello.c's direct compile errors does NOT mention an unresolved GC_init reference - this looks like a different, unexpected regression:" - echo "gc_alloc.c: $direct_err_gc_alloc" - echo "hello.c: $direct_err_hello" + echo "::error::libgc.a lane failed with the known summary shape, but at least one of gc_alloc.c/hello.c's direct compile errors does NOT mention an unresolved GC_init reference (or exited abnormally) - this looks like a different, unexpected regression:" + echo "gc_alloc.c: exit=$direct_code_gc_alloc: $direct_err_gc_alloc" + echo "hello.c: exit=$direct_code_hello: $direct_err_hello" exit 1 fi ;; From f08211f30f61249693d5d07cbb8b5ef25a5e2be6 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sun, 26 Jul 2026 19:25:28 -0400 Subject: [PATCH 23/30] ci: gate the static-link claim on its test outcome; reject additional errors in the checked-in XFAIL Fixes both findings from pullrequestreview-4782525602 on vlang/tccbin#74: - P2: the packaged KNOWN_ISSUES.txt unconditionally claimed "lib/libgc.a CANNOT be linked... confirmed via a real CI run" even when the static XFAIL step failed with an unexpected signature or unexpectedly passed in THIS run - the claim didn't reflect what this specific run actually verified, same class of gap already fixed for the dylib/no-GC claims. Added `id: static_xfail_test` and now derives the static-link text from its real outcome the same way. Verified both branches render correctly (all-success, and static-XFAIL-failed). - P2: the checked-in-binary XFAIL accepted the known "library 'c' not found" diagnostic even if a wholly different, unrelated tcc error was ALSO present alongside it - a bare substring-present check never looked at what else came back. Now rejects unless every tcc error line is specifically the known libc diagnostic, matching the same exclusivity pattern already applied to the libgc.a XFAIL probes. Verified empirically: a mocked "known error + unrelated error" case is now correctly rejected. --- .github/workflows/build-and-test.yml | 36 ++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 1ad0a38..4671eb8 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -126,6 +126,22 @@ jobs: fi case "$direct_err_checkedin" in *"library 'c' not found"*) + # A future checked-in tcc.exe could still emit the + # expected "library 'c' not found" line while ALSO + # reporting a wholly different, unrelated error (a + # malformed object, a wrong-architecture failure) - + # a bare substring-present check would accept that + # too, since it never looks at what else came back + # (Codex pullrequestreview-4782525602 on vlang/ + # tccbin#74, same class of gap already fixed for the + # libgc.a XFAIL probes). Reject unless EVERY tcc + # error line is specifically the known libc diagnostic. + other_checkedin=$(printf '%s\n' "$direct_err_checkedin" | grep -E '^tcc: error:' | grep -v -F "library 'c' not found") + if [ -n "$other_checkedin" ]; then + echo "::error::The checked-in tcc.exe/libgc.a failed with the known aggregate count and the known libc diagnostic is present, but at least one OTHER tcc error line was also reported - this looks like a different, unexpected regression riding alongside the known one:" + echo "$other_checkedin" + exit 1 + fi echo "known XFAIL: the checked-in tcc.exe/libgc.a fail all 3 shared tests (stale binary, broken libc.dylib symlink - library 'c' not found), as expected - not blocking CI" ;; *) @@ -457,6 +473,7 @@ jobs: # parsing got fixed) still fails the job for real instead of being # silently absorbed. - name: run shared conformance tests (libgc.a, static - XFAIL) + id: static_xfail_test working-directory: work if: ${{ !cancelled() }} run: | @@ -592,13 +609,26 @@ jobs: # to reproduce against (mirroring macos-arm64's own rationale for # `!cancelled()` here) - but make the claim match what THIS run # actually verified, using the two prior steps' real outcomes. + # The static-link claim below (that libgc.a can't be linked by + # this tcc.exe) was, until now, ALSO printed unconditionally - if + # the static XFAIL step above failed with an unexpected signature, + # or unexpectedly passed, this artifact would still assert the + # known archive-parsing diagnosis as if this run had confirmed it + # (Codex pullrequestreview-4782525602 on vlang/tccbin#74, same + # class of gap as the dylib/no-GC claims fixed just below it). - name: package rebuilt tcc for upload if: ${{ !cancelled() }} working-directory: work env: DYLIB_TEST_OUTCOME: ${{ steps.dylib_test.outcome }} NOGC_TEST_OUTCOME: ${{ steps.nogc_test.outcome }} + STATIC_XFAIL_TEST_OUTCOME: ${{ steps.static_xfail_test.outcome }} run: | + if [ "$STATIC_XFAIL_TEST_OUTCOME" = "success" ]; then + static_claim="lib/libgc.a (static archive) CANNOT be linked by this tcc.exe - confirmed via a real CI run: tcc reports \"unresolved reference to '_GC_init'\" etc. even though the symbol is present and well-formed in the archive (a tcc Mach-O archive-parsing limitation, the same one already documented on macos-arm64)." + else + static_claim="THIS BUILD'S OWN CI VALIDATION OF THE STATIC libgc.a LINK FAILURE DID NOT CONFIRM THE EXPECTED SIGNATURE (static XFAIL test outcome: $STATIC_XFAIL_TEST_OUTCOME) - do not assume lib/libgc.a's link status from this specific artifact without checking the workflow run's log." + fi if [ "$DYLIB_TEST_OUTCOME" = "success" ] && [ "$NOGC_TEST_OUTCOME" = "success" ]; then dylib_claim="Use lib/libgc.dylib with -Wl,-rpath, instead; that link path is verified working in this build's own CI run." else @@ -608,11 +638,7 @@ jobs: This is a CI-verification build, not a drop-in replacement for the committed tccbin-macos-amd64 distribution. - lib/libgc.a (static archive) CANNOT be linked by this tcc.exe - - confirmed via a real CI run: tcc reports "unresolved reference to - '_GC_init'" etc. even though the symbol is present and well-formed - in the archive (a tcc Mach-O archive-parsing limitation, the same - one already documented on macos-arm64). $dylib_claim + $static_claim $dylib_claim V's existing builtin_d_gcboehm.c.v for macOS amd64+tinyc still selects the static libgc.a - do not switch it to the dylib until a From 7c65c6cf9dd6c0b998415f3b262144e652b51599 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sun, 26 Jul 2026 19:56:10 -0400 Subject: [PATCH 24/30] ci: fix set -e + pipefail silently killing the checked-in-binary XFAIL on the expected-good path Fixes P1 finding from pullrequestreview-4782839244 on vlang/tccbin#74, a real CI failure this exact bug caused (run 30225228663): the previous commit's `other_checkedin=$(... | grep -v ...)` was a standalone assignment, not a function invoked from an if-condition. In the EXPECTED case (no other error present), the final `grep -v` legitimately finds nothing to filter and exits 1 (grep's normal "no match" status) - and since GitHub's bash runs with pipefail, that nonzero pipeline status killed the assignment statement itself under `set -e`, silently aborting the whole step with zero output, in exactly the case that should have reached "known XFAIL". Reproduced directly: `bash -c "set -e; x=\$(printf ... | grep -v ...); echo after"` never prints "after". Refactored into is_known_checkedin_error(), a function invoked as an if-condition (`if is_known_checkedin_error ...; then`) - bash suspends errexit for the entire dynamic extent of evaluating an if/while/until condition, including anything a called function does internally, which is exactly why the sibling is_known_gc_init/is_known_etext_end functions never had this problem despite the identical internal grep -v pattern. Verified: reproduced the silent-death bug in isolation first, confirmed the function-based refactor survives both the expected-good case and the known-error-plus-unrelated-error case, and confirmed the already-working is_known_gc_init's `&&`-chained if-condition usage is genuinely errexit-safe too. --- .github/workflows/build-and-test.yml | 88 ++++++++++++++++------------ 1 file changed, 52 insertions(+), 36 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 4671eb8..6a5bd81 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -110,46 +110,62 @@ jobs: direct_code_checkedin=$? set -e + # `other_checkedin=$(... | grep -v ...)` as a standalone + # assignment (not a function called from an if-condition) is + # NOT safe under `set -e`: in the expected-good case (no OTHER + # error present), the final `grep -v` legitimately finds + # nothing to filter and exits 1 (grep's normal "no match" + # status, not an error) - and since GitHub's bash runs with + # pipefail, that nonzero pipeline status kills the assignment + # statement itself, aborting the whole step SILENTLY, with no + # echo, right in the case that should reach "known XFAIL" + # (Codex pullrequestreview-4782839244 on vlang/tccbin#74, + # reproduced directly: `bash -c 'set -e; x=$(printf ... | + # grep -v ...); echo after'` never prints "after"). Wrapping + # this in a function invoked as an if-condition is safe + # instead: bash suspends errexit for the entire dynamic extent + # of evaluating an if/while/until condition, including + # anything a called function does internally - exactly how + # is_known_gc_init/is_known_etext_end already work elsewhere + # in this same file. + is_known_checkedin_error() { + # $1 = captured stderr text, $2 = the probe's own exit code. + # A regression could make tcc print the expected "library + # 'c' not found" diagnostic and then crash/abort rather than + # exiting cleanly - the earlier `|| true` discarded the exit + # code entirely (same class of gap Codex flagged in + # pullrequestreview-4781865117 on vlang/tccbin#75 for the + # libgc.a XFAIL probes). Reject a signal-terminated exit + # (128+signal) even if the expected text is present. + if [ "$2" -gt 128 ]; then + return 1 + fi + case "$1" in + *"library 'c' not found"*) ;; + *) return 1 ;; + esac + # A future checked-in tcc.exe could still emit the expected + # "library 'c' not found" line while ALSO reporting a wholly + # different, unrelated error (a malformed object, a wrong- + # architecture failure) - a bare substring-present check + # would accept that too, since it never looks at what else + # came back (Codex pullrequestreview-4782525602 on vlang/ + # tccbin#74, same class of gap already fixed for the + # libgc.a XFAIL probes). Reject unless EVERY tcc error line + # is specifically the known libc diagnostic. + other_checkedin=$(printf '%s\n' "$1" | grep -E '^tcc: error:' | grep -v -F "library 'c' not found") + [ -z "$other_checkedin" ] + } + case "$output" in *"0 passed, 3 failed"*) - # A regression could make tcc print the expected "library - # 'c' not found" diagnostic and then crash/abort rather - # than exiting cleanly - the earlier `|| true` discarded - # the exit code entirely (same class of gap Codex flagged - # in pullrequestreview-4781865117 on vlang/tccbin#75 for - # the libgc.a XFAIL probes). Reject a signal-terminated - # exit (128+signal) even if the expected text is present. - if [ "$direct_code_checkedin" -gt 128 ]; then - echo "::error::The checked-in tcc.exe/libgc.a failed with the known aggregate count, but the direct crash.c probe exited abnormally (exit=$direct_code_checkedin, signal-terminated) rather than tcc's normal compile-error status - this looks like a different, unexpected regression:" - echo "$direct_err_checkedin" + if is_known_checkedin_error "$direct_err_checkedin" "$direct_code_checkedin"; then + echo "known XFAIL: the checked-in tcc.exe/libgc.a fail all 3 shared tests (stale binary, broken libc.dylib symlink - library 'c' not found), as expected - not blocking CI" + else + echo "::error::The checked-in tcc.exe/libgc.a failed with the known aggregate count, but the direct crash.c compile error does NOT match the known broken-libc.dylib-symlink signature exactly (missing, abnormally terminated, or accompanied by another error) - this looks like a different, unexpected regression:" + echo "exit=$direct_code_checkedin: $direct_err_checkedin" exit 1 fi - case "$direct_err_checkedin" in - *"library 'c' not found"*) - # A future checked-in tcc.exe could still emit the - # expected "library 'c' not found" line while ALSO - # reporting a wholly different, unrelated error (a - # malformed object, a wrong-architecture failure) - - # a bare substring-present check would accept that - # too, since it never looks at what else came back - # (Codex pullrequestreview-4782525602 on vlang/ - # tccbin#74, same class of gap already fixed for the - # libgc.a XFAIL probes). Reject unless EVERY tcc - # error line is specifically the known libc diagnostic. - other_checkedin=$(printf '%s\n' "$direct_err_checkedin" | grep -E '^tcc: error:' | grep -v -F "library 'c' not found") - if [ -n "$other_checkedin" ]; then - echo "::error::The checked-in tcc.exe/libgc.a failed with the known aggregate count and the known libc diagnostic is present, but at least one OTHER tcc error line was also reported - this looks like a different, unexpected regression riding alongside the known one:" - echo "$other_checkedin" - exit 1 - fi - echo "known XFAIL: the checked-in tcc.exe/libgc.a fail all 3 shared tests (stale binary, broken libc.dylib symlink - library 'c' not found), as expected - not blocking CI" - ;; - *) - echo "::error::The checked-in tcc.exe/libgc.a failed with the known aggregate count, but the direct crash.c compile error does NOT mention the known broken-libc.dylib-symlink signature - this looks like a different, unexpected regression:" - echo "$direct_err_checkedin" - exit 1 - ;; - esac ;; *) echo "::error::The checked-in tcc.exe/libgc.a failed, but NOT with the known/expected summary (0 passed, 3 failed) - this looks like a different, unexpected regression in the binaries this PR proposes to commit:" From bb7beb0d1927eff14dcef7273b4de0634546aaeb Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sun, 26 Jul 2026 23:32:33 -0400 Subject: [PATCH 25/30] ci: reject exit-0 in XFAIL signature checks; independently validate both checked-in and rebuilt libgc.a via ar/nm Fixes all 3 findings from pullrequestreview-4783389496 on vlang/tccbin#74: - P2: is_known_checkedin_error and is_known_gc_init only rejected signal-terminated exits (>128), never exit 0 - a changed tcc.exe reporting the expected diagnostic text as noise while still succeeding overall would still be accepted as the known XFAIL. Verified empirically: exit=0 with the known text is now rejected, exit=1 with the known text is still accepted. - P1: crash.c (the checked-in-binary signature probe) never references any GC symbol, so it never actually exercises thirdparty/tcc/lib/libgc.a - a PR replacing the checked-in libgc.a with a corrupt/empty/wrong-architecture archive would produce the exact same "0 passed, 3 failed" + "library 'c' not found" signature and pass CI undetected. Added a direct ar t / nm -g check on the checked-in archive (confirming it's well-formed and defines GC_init) before the rebuild step overwrites it. - P2: the static libgc.a XFAIL accepted "unresolved reference to '_GC_init'" as proof of tcc's known archive-parsing limitation without ever confirming the REBUILT archive actually defines that symbol - an empty/incomplete bdwgc build would produce the identical error text and get misclassified as the harmless known limitation. Added the same ar t / nm -g check on the rebuilt archive before accepting the XFAIL, so KNOWN_ISSUES.txt's claim that the symbol is "present and well-formed" is now actually backed by verification. Verified the nm -g grep pattern against both the real observed output format from earlier this session (T = defined) and a mocked broken archive (U = undefined only), confirming it distinguishes them correctly. Same exit-0 rejection also applied to the sibling freebsd-amd64 workflow's is_known_etext_end for parity. --- .github/workflows/build-and-test.yml | 61 ++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 6a5bd81..1386775 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -78,6 +78,29 @@ jobs: # rather than a guess. otool -l thirdparty/tcc/tcc.exe | grep -A3 "LC_VERSION_MIN_MACOSX\|LC_BUILD_VERSION" || echo "(no LC_VERSION_MIN_MACOSX/LC_BUILD_VERSION load command found)" + # crash.c (used below as the signature probe) never + # references any GC symbol, so it never actually exercises + # thirdparty/tcc/lib/libgc.a - if a PR replaced the checked-in + # libgc.a with a corrupt, empty, or wrong-architecture + # archive while leaving tcc.exe's stale libc.dylib symlink + # broken, this whole step would still see the same "0 passed, + # 3 failed" aggregate and the same "library 'c' not found" + # signature, since nothing here ever actually touches + # libgc.a's contents - a real archive regression would pass + # CI completely undetected (Codex pullrequestreview- + # 4783389496 on vlang/tccbin#74). Validate the checked-in + # archive directly via ar/nm, independent of tcc.exe's own + # (broken) linking, before the rebuild step below overwrites + # it. + if ! ar t thirdparty/tcc/lib/libgc.a >/dev/null 2>&1; then + echo "::error::thirdparty/tcc/lib/libgc.a is not a well-formed archive (ar t failed) - this is a real regression in the checked-in archive this PR proposes to commit, not the known broken-libc.dylib-symlink issue." + exit 1 + fi + if ! nm -g thirdparty/tcc/lib/libgc.a 2>&1 | grep -qE '(^| )[Tt] _?GC_init$'; then + echo "::error::thirdparty/tcc/lib/libgc.a does not define GC_init - this is a real regression in the checked-in archive this PR proposes to commit (missing/incomplete symbols), not the known broken-libc.dylib-symlink issue." + exit 1 + fi + set +e output=$(thirdparty/tccbin_tests/run.sh thirdparty/tcc/tcc.exe macos -- \ -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ @@ -136,8 +159,13 @@ jobs: # code entirely (same class of gap Codex flagged in # pullrequestreview-4781865117 on vlang/tccbin#75 for the # libgc.a XFAIL probes). Reject a signal-terminated exit - # (128+signal) even if the expected text is present. - if [ "$2" -gt 128 ]; then + # (128+signal) even if the expected text is present. Also + # reject exit 0: a changed tcc.exe could start printing this + # same diagnostic as noise while still reporting overall + # success (e.g. without producing the requested executable) + # - the text alone isn't proof the compile actually failed + # (Codex pullrequestreview-4783389496 on vlang/tccbin#74). + if [ "$2" -eq 0 ] || [ "$2" -gt 128 ]; then return 1 fi case "$1" in @@ -493,6 +521,26 @@ jobs: working-directory: work if: ${{ !cancelled() }} run: | + # If the rebuild produced a structurally valid but empty or + # incomplete libgc.a (a real, different regression from a + # broken bdwgc build), both direct probes below would still + # show only "unresolved reference to '_GC_init'" text - the + # exact same shape as tcc's own known archive-parsing + # limitation - and KNOWN_ISSUES.txt would claim the symbol is + # "present and well-formed" even though nothing here ever + # checked that (Codex pullrequestreview-4783389496 on vlang/ + # tccbin#74). Validate the rebuilt archive directly via ar/nm + # before treating any link failure against it as the known, + # harmless tcc limitation. + if ! ar t ./thirdparty/tcc/lib/libgc.a >/dev/null 2>&1; then + echo "::error::thirdparty/tcc/lib/libgc.a is not a well-formed archive (ar t failed) right after being rebuilt - this is a real regression in the bdwgc build, not the known tcc archive-parsing limitation." + exit 1 + fi + if ! nm -g ./thirdparty/tcc/lib/libgc.a 2>&1 | grep -qE '(^| )[Tt] _?GC_init$'; then + echo "::error::thirdparty/tcc/lib/libgc.a does not define GC_init right after being rebuilt - this is a real regression in the bdwgc build (missing/incomplete symbols), not the known tcc archive-parsing limitation." + exit 1 + fi + set +e output=$(./thirdparty/tccbin_tests/run.sh ./thirdparty/tcc/tcc.exe macos -- \ -DGC_BUILTIN_ATOMIC=1 -DMPROTECT_VDB=1 \ @@ -561,8 +609,13 @@ jobs: # workflow, vlang/tccbin#75, same class of gap here). A # POSIX shell reports a signal-terminated process as exit # code 128+signal; reject those instead of treating any - # nonzero exit as "the expected compile-error status". - if [ "$2" -gt 128 ]; then + # nonzero exit as "the expected compile-error status". Also + # reject exit 0: a changed tcc.exe could start printing this + # same diagnostic as noise while still reporting overall + # success - the text alone isn't proof the compile actually + # failed (Codex pullrequestreview-4783389496 on vlang/ + # tccbin#74). + if [ "$2" -eq 0 ] || [ "$2" -gt 128 ]; then return 1 fi case "$1" in From 3ba576c9daf6074a83dbca493ba7fb97ce4b7f2e Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Mon, 27 Jul 2026 07:46:34 -0400 Subject: [PATCH 26/30] ci: fix SIGPIPE+pipefail false failure in archive checks; verify checked-in archive architecture; assert tcc.exe's committed file mode Fixes all 3 findings from pullrequestreview-4783470598 on vlang/tccbin#74, the first of which broke the CI run immediately after the previous commit (run 30234854073): - P1: `nm -g archive | grep -qE ...` is unsafe under GitHub's pipefail bash - grep -q stops reading as soon as it finds a match, which can make nm receive SIGPIPE while still writing the rest of a large archive's symbol list, and pipefail then reports the WHOLE pipeline as failed even though grep found the match. Reproduced directly: a large mocked producer piped through grep -qE under set -eo pipefail reported "not found" despite matching on the very first line. Fixed by capturing nm's full output into a variable first (command substitution fully drains the producer, no early exit possible) and grepping the captured text without -q. Applied to both the checked-in and rebuilt archive checks, as flagged. - P1: a well-formed, correctly-symboled archive for the WRONG architecture (e.g. an accidentally-committed arm64 build) would have passed both the ar t and nm checks, since neither validated architecture, and the crash.c signature probe never touches libgc.a at all. Added a `lipo -info` check requiring x86_64 before trusting anything else about the checked-in archive. - P2: the unconditional `chmod +x thirdparty/tcc/tcc.exe` silently repaired a potentially-wrong committed file mode before the "as distributed" verification ran against it - a PR that accidentally committed tcc.exe as non-executable would still pass CI, even though a real consumer checking out that exact commit gets the actual (broken) mode. Replaced with an assertion that the committed mode is already correct. Also fixed a second occurrence of the same "standalone grep assignment under set -e" bug this exact PR already hit once (pullrequestreview- 4782839244): the new `grep -E` (without -q) capture still needs its own `|| true`, since a genuinely missing symbol makes grep exit 1 with nothing to catch it. Verified the complete fix against 3 scenarios: symbol present (small output), symbol genuinely missing, and symbol present with a large trailing output (the SIGPIPE-prone case) - all three now report correctly and the script completes without dying. --- .github/workflows/build-and-test.yml | 63 ++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 1386775..3e5b077 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -63,7 +63,19 @@ jobs: - name: verify the checked-in tcc.exe (as distributed, before rebuild) working-directory: work run: | - chmod +x thirdparty/tcc/tcc.exe + # A PR that accidentally committed tcc.exe with the wrong + # (non-executable) file mode would still pass this "as + # distributed" verification, since the unconditional chmod + # below silently repaired it first - but a real consumer + # checking out this exact commit gets the actual committed + # mode, not this CI job's repaired one (Codex + # pullrequestreview-4783470598 on vlang/tccbin#74). Assert + # the committed mode is already correct instead of silently + # fixing it. + if [ ! -x thirdparty/tcc/tcc.exe ]; then + echo "::error::thirdparty/tcc/tcc.exe is not committed as executable - a consumer checking out this exact commit would get a non-executable file. This is a real regression in what this PR proposes to commit, not something CI should silently repair before testing." + exit 1 + fi # Informational: what deployment target does the CURRENTLY # distributed binary actually support? Neither this workflow's @@ -96,7 +108,38 @@ jobs: echo "::error::thirdparty/tcc/lib/libgc.a is not a well-formed archive (ar t failed) - this is a real regression in the checked-in archive this PR proposes to commit, not the known broken-libc.dylib-symlink issue." exit 1 fi - if ! nm -g thirdparty/tcc/lib/libgc.a 2>&1 | grep -qE '(^| )[Tt] _?GC_init$'; then + # A well-formed, correctly-symboled archive for the WRONG + # architecture (e.g. an accidentally-committed arm64 build) + # would pass both this check and the one below - the direct + # signature probe (crash.c) never touches libgc.a at all, so + # nothing else here would catch it either (Codex + # pullrequestreview-4783470598 on vlang/tccbin#74). Confirm + # the archive is actually x86_64 before trusting anything + # else about it. + checkedin_arch=$(lipo -info thirdparty/tcc/lib/libgc.a 2>&1) || true + case "$checkedin_arch" in + *"x86_64"*) ;; + *) + echo "::error::thirdparty/tcc/lib/libgc.a is not an x86_64 archive (lipo -info: $checkedin_arch) - this is a real regression (wrong architecture) in the checked-in archive this PR proposes to commit, not the known broken-libc.dylib-symlink issue." + exit 1 + ;; + esac + # `grep -q` stops reading as soon as it finds a match, which + # can make `nm` receive SIGPIPE while still writing the rest + # of a large archive's symbol list - under GitHub's bash + # (pipefail enabled), that SIGPIPE-killed `nm` makes the WHOLE + # pipeline report failure even though grep itself found the + # match, so this reported "does not define GC_init" on every + # run regardless of the archive's actual contents (Codex + # pullrequestreview-4783470598 on vlang/tccbin#74, reproduced + # directly: a large mocked producer piped through `grep -qE` + # under `set -eo pipefail` reported "not found" even though + # the very first line matched). Capture the full output first + # (command substitution fully drains the producer, no early + # exit possible) and grep the captured text without -q. + checkedin_gc_symbols=$(nm -g thirdparty/tcc/lib/libgc.a 2>&1) || true + checkedin_gc_init_defined=$(printf '%s\n' "$checkedin_gc_symbols" | grep -E '(^| )[Tt] _?GC_init$') || true + if [ -z "$checkedin_gc_init_defined" ]; then echo "::error::thirdparty/tcc/lib/libgc.a does not define GC_init - this is a real regression in the checked-in archive this PR proposes to commit (missing/incomplete symbols), not the known broken-libc.dylib-symlink issue." exit 1 fi @@ -536,7 +579,21 @@ jobs: echo "::error::thirdparty/tcc/lib/libgc.a is not a well-formed archive (ar t failed) right after being rebuilt - this is a real regression in the bdwgc build, not the known tcc archive-parsing limitation." exit 1 fi - if ! nm -g ./thirdparty/tcc/lib/libgc.a 2>&1 | grep -qE '(^| )[Tt] _?GC_init$'; then + # `grep -q` stops reading as soon as it finds a match, which + # can make `nm` receive SIGPIPE while still writing the rest + # of a large archive's symbol list - under GitHub's bash + # (pipefail enabled), that SIGPIPE-killed `nm` makes the WHOLE + # pipeline report failure even though grep itself found the + # match, so this reported "does not define GC_init" on every + # run regardless of the archive's actual contents (Codex + # pullrequestreview-4783470598 on vlang/tccbin#74 - the exact + # bug that broke run 30234854073, the CI run right after this + # check was first added). Capture the full output first + # (command substitution fully drains the producer, no early + # exit possible) and grep the captured text without -q. + rebuilt_gc_symbols=$(nm -g ./thirdparty/tcc/lib/libgc.a 2>&1) || true + rebuilt_gc_init_defined=$(printf '%s\n' "$rebuilt_gc_symbols" | grep -E '(^| )[Tt] _?GC_init$') || true + if [ -z "$rebuilt_gc_init_defined" ]; then echo "::error::thirdparty/tcc/lib/libgc.a does not define GC_init right after being rebuilt - this is a real regression in the bdwgc build (missing/incomplete symbols), not the known tcc archive-parsing limitation." exit 1 fi From 442b25b9f1d9a533f89628f003fc729dada4e46a Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Mon, 27 Jul 2026 07:56:05 -0400 Subject: [PATCH 27/30] ci: diagnose why ar t is failing on the checked-in libgc.a (previously swallowed the error text) --- .github/workflows/build-and-test.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 3e5b077..31e8924 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -104,7 +104,21 @@ jobs: # archive directly via ar/nm, independent of tcc.exe's own # (broken) linking, before the rebuild step below overwrites # it. - if ! ar t thirdparty/tcc/lib/libgc.a >/dev/null 2>&1; then + # TEMPORARY diagnostic: the previous version of this check + # (redirecting ar t's own stderr to /dev/null) failed on a + # real CI run with no visible reason - capture and print the + # actual error before deciding whether this is a genuine new + # regression or a pre-existing characteristic of this ancient + # archive (paired with the equally-ancient checked-in + # v0.9.27 tcc.exe) that a real ar/nm can't parse regardless. + set +e + ar_t_output=$(ar t thirdparty/tcc/lib/libgc.a 2>&1) + ar_t_code=$? + set -e + echo "ar t output: $ar_t_output" + echo "ar t exit code: $ar_t_code" + file thirdparty/tcc/lib/libgc.a || true + if [ "$ar_t_code" -ne 0 ]; then echo "::error::thirdparty/tcc/lib/libgc.a is not a well-formed archive (ar t failed) - this is a real regression in the checked-in archive this PR proposes to commit, not the known broken-libc.dylib-symlink issue." exit 1 fi From 688698f37cde4619ab5866ddca3d5eed807782a6 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Mon, 27 Jul 2026 07:59:40 -0400 Subject: [PATCH 28/30] ci: extract the x86_64 slice from the checked-in FAT libgc.a before validating it with ar/nm Root-caused via the diagnostic pushed in commit 442b25b: the checked-in libgc.a is a FAT/universal binary containing BOTH x86_64 and arm64 slices ("Mach-O universal binary with 2 architectures"), not a plain single-arch archive. macOS's `ar` explicitly refuses to read a fat archive directly ("ar: ... is a fat file (use libtool(1) or lipo(1) and ar(1) on it)"), which is why every previous version of this check failed on every run regardless of the archive's actual (valid) contents - it wasn't a code bug or a real regression, just an untested assumption about this specific file's format. Replaced the separate `ar t` + `lipo -info` substring-match approach (which could also have been fooled by an arm64-only archive that still contains the literal text "x86_64" somewhere in a multi-arch `lipo -info` listing) with `lipo -thin x86_64 ... -output ...`: this extracts the x86_64 slice explicitly - failing if it doesn't exist, closing the architecture-verification gap - and produces a normal thin archive that `ar t`/`nm -g` can then read directly, closing the well-formed-archive and GC-symbol-defined checks the same way. --- .github/workflows/build-and-test.yml | 62 +++++++++++----------------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 31e8924..d5db827 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -101,43 +101,31 @@ jobs: # libgc.a's contents - a real archive regression would pass # CI completely undetected (Codex pullrequestreview- # 4783389496 on vlang/tccbin#74). Validate the checked-in - # archive directly via ar/nm, independent of tcc.exe's own - # (broken) linking, before the rebuild step below overwrites - # it. - # TEMPORARY diagnostic: the previous version of this check - # (redirecting ar t's own stderr to /dev/null) failed on a - # real CI run with no visible reason - capture and print the - # actual error before deciding whether this is a genuine new - # regression or a pre-existing characteristic of this ancient - # archive (paired with the equally-ancient checked-in - # v0.9.27 tcc.exe) that a real ar/nm can't parse regardless. - set +e - ar_t_output=$(ar t thirdparty/tcc/lib/libgc.a 2>&1) - ar_t_code=$? - set -e - echo "ar t output: $ar_t_output" - echo "ar t exit code: $ar_t_code" - file thirdparty/tcc/lib/libgc.a || true - if [ "$ar_t_code" -ne 0 ]; then - echo "::error::thirdparty/tcc/lib/libgc.a is not a well-formed archive (ar t failed) - this is a real regression in the checked-in archive this PR proposes to commit, not the known broken-libc.dylib-symlink issue." + # archive directly via lipo/ar/nm, independent of tcc.exe's + # own (broken) linking, before the rebuild step below + # overwrites it. + # + # This checked-in libgc.a is a FAT/universal binary + # containing BOTH x86_64 and arm64 slices (confirmed via a + # real CI run's `file` output: "Mach-O universal binary with + # 2 architectures: [x86_64:...] [arm64:...]") - macOS's plain + # `ar` refuses to read a fat archive directly ("ar: ... is a + # fat file (use libtool(1) or lipo(1) and ar(1) on it)"), and + # a naive x86_64-only `lipo -info` substring match would have + # also passed for an ARM64-ONLY archive that merely happens + # to have "x86_64" appear elsewhere in a fat listing (Codex + # pullrequestreview-4783470598 on vlang/tccbin#74). Extract + # the x86_64 slice explicitly first - this validates the + # slice actually exists AND gives a normal thin archive that + # `ar`/`nm` can read directly, closing both gaps at once. + if ! lipo -thin x86_64 thirdparty/tcc/lib/libgc.a -output /tmp/checkedin_libgc_x86_64.a 2>&1; then + echo "::error::thirdparty/tcc/lib/libgc.a does not contain an x86_64 slice (lipo -thin x86_64 failed) - this is a real regression (wrong/missing architecture) in the checked-in archive this PR proposes to commit, not the known broken-libc.dylib-symlink issue." + exit 1 + fi + if ! ar t /tmp/checkedin_libgc_x86_64.a >/dev/null 2>&1; then + echo "::error::thirdparty/tcc/lib/libgc.a's x86_64 slice is not a well-formed archive (ar t failed) - this is a real regression in the checked-in archive this PR proposes to commit, not the known broken-libc.dylib-symlink issue." exit 1 fi - # A well-formed, correctly-symboled archive for the WRONG - # architecture (e.g. an accidentally-committed arm64 build) - # would pass both this check and the one below - the direct - # signature probe (crash.c) never touches libgc.a at all, so - # nothing else here would catch it either (Codex - # pullrequestreview-4783470598 on vlang/tccbin#74). Confirm - # the archive is actually x86_64 before trusting anything - # else about it. - checkedin_arch=$(lipo -info thirdparty/tcc/lib/libgc.a 2>&1) || true - case "$checkedin_arch" in - *"x86_64"*) ;; - *) - echo "::error::thirdparty/tcc/lib/libgc.a is not an x86_64 archive (lipo -info: $checkedin_arch) - this is a real regression (wrong architecture) in the checked-in archive this PR proposes to commit, not the known broken-libc.dylib-symlink issue." - exit 1 - ;; - esac # `grep -q` stops reading as soon as it finds a match, which # can make `nm` receive SIGPIPE while still writing the rest # of a large archive's symbol list - under GitHub's bash @@ -151,10 +139,10 @@ jobs: # the very first line matched). Capture the full output first # (command substitution fully drains the producer, no early # exit possible) and grep the captured text without -q. - checkedin_gc_symbols=$(nm -g thirdparty/tcc/lib/libgc.a 2>&1) || true + checkedin_gc_symbols=$(nm -g /tmp/checkedin_libgc_x86_64.a 2>&1) || true checkedin_gc_init_defined=$(printf '%s\n' "$checkedin_gc_symbols" | grep -E '(^| )[Tt] _?GC_init$') || true if [ -z "$checkedin_gc_init_defined" ]; then - echo "::error::thirdparty/tcc/lib/libgc.a does not define GC_init - this is a real regression in the checked-in archive this PR proposes to commit (missing/incomplete symbols), not the known broken-libc.dylib-symlink issue." + echo "::error::thirdparty/tcc/lib/libgc.a's x86_64 slice does not define GC_init - this is a real regression in the checked-in archive this PR proposes to commit (missing/incomplete symbols), not the known broken-libc.dylib-symlink issue." exit 1 fi From 114449d2d2b47e571293efb257749c151df7e9a2 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Mon, 27 Jul 2026 08:05:07 -0400 Subject: [PATCH 29/30] ci: refine the checked-in XFAIL's exit-0 rejection to only reject when a binary was actually produced Root-caused via a real CI run: this ancient checked-in v0.9.27 tcc.exe exits 0 while still printing "tcc: error: library 'c' not found" and producing NO runnable binary - a genuine quirk/bug of this specific old build, not a new regression. The previous commit's blanket "reject exit 0" was too broad and made this check permanently fail on this branch's own known-good state. The original finding's actual concern (Codex pullrequestreview- 4783389496) was specifically "a changed tcc.exe... while still reporting overall success... without producing the requested executable" - i.e. exit 0 AND an actual working binary is the real problem, not exit 0 alone. Now checks whether the probe's output file is executable and only rejects the exit-0 case when combined with that: if [ "$2" -eq 0 ] && [ "$3" -eq 1 ]; then return 1 # genuinely succeeded despite the noise - real problem fi Verified against all 3 scenarios: exit=0 with no binary produced (the observed old-tcc behavior) is now accepted, exit=0 with a binary produced is still rejected, and the normal exit=1 case is unaffected. Left is_known_gc_init (the rebuilt/modern tcc's equivalent check) unchanged for now - no evidence yet that the actively-maintained mob branch has the same exit-code quirk as this 15+-year-old checked-in binary, and speculative changes without a reproduced failure aren't worth the added risk on an already-long fix chain. --- .github/workflows/build-and-test.yml | 45 +++++++++++++++++++++------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index d5db827..b301081 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -172,11 +172,25 @@ jobs: # error already root-caused earlier this session: tcc reports # "library 'c' not found" when its lib/libc.dylib symlink # target doesn't exist. + rm -f /tmp/crash_checkedin_probe set +e direct_err_checkedin=$(thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/crash.c \ -o /tmp/crash_checkedin_probe 2>&1) direct_code_checkedin=$? set -e + # This ancient, checked-in v0.9.27 tcc.exe has been observed + # (via a real CI run) to exit 0 while STILL printing "tcc: + # error: library 'c' not found" and NOT producing a runnable + # binary - a real quirk/bug of this specific old build, not a + # new regression. Whether the probe actually produced a + # runnable executable is a more direct, reliable signal of + # "did the compile really fail" than trusting this old + # binary's own exit code. + if [ -x /tmp/crash_checkedin_probe ]; then + direct_produced_binary_checkedin=1 + else + direct_produced_binary_checkedin=0 + fi # `other_checkedin=$(... | grep -v ...)` as a standalone # assignment (not a function called from an if-condition) is @@ -197,20 +211,29 @@ jobs: # is_known_gc_init/is_known_etext_end already work elsewhere # in this same file. is_known_checkedin_error() { - # $1 = captured stderr text, $2 = the probe's own exit code. + # $1 = captured stderr text, $2 = the probe's own exit code, + # $3 = 1 if the probe actually produced a runnable binary, + # 0 otherwise. # A regression could make tcc print the expected "library # 'c' not found" diagnostic and then crash/abort rather than # exiting cleanly - the earlier `|| true` discarded the exit # code entirely (same class of gap Codex flagged in # pullrequestreview-4781865117 on vlang/tccbin#75 for the # libgc.a XFAIL probes). Reject a signal-terminated exit - # (128+signal) even if the expected text is present. Also - # reject exit 0: a changed tcc.exe could start printing this - # same diagnostic as noise while still reporting overall - # success (e.g. without producing the requested executable) - # - the text alone isn't proof the compile actually failed - # (Codex pullrequestreview-4783389496 on vlang/tccbin#74). - if [ "$2" -eq 0 ] || [ "$2" -gt 128 ]; then + # (128+signal) even if the expected text is present. + if [ "$2" -gt 128 ]; then + return 1 + fi + # Exit 0 alone isn't disqualifying: this ancient checked-in + # tcc.exe has been observed to exit 0 while still printing + # the expected error and producing NO runnable binary (a + # real quirk/bug of this specific old build). What matters + # is whether a changed tcc.exe reports overall success AND + # actually produces a working executable despite the noise + # - that combination is the real problem the exit-0 check + # was meant to catch (Codex pullrequestreview-4783389496 on + # vlang/tccbin#74); exit 0 with no binary produced is not. + if [ "$2" -eq 0 ] && [ "$3" -eq 1 ]; then return 1 fi case "$1" in @@ -232,11 +255,11 @@ jobs: case "$output" in *"0 passed, 3 failed"*) - if is_known_checkedin_error "$direct_err_checkedin" "$direct_code_checkedin"; then + if is_known_checkedin_error "$direct_err_checkedin" "$direct_code_checkedin" "$direct_produced_binary_checkedin"; then echo "known XFAIL: the checked-in tcc.exe/libgc.a fail all 3 shared tests (stale binary, broken libc.dylib symlink - library 'c' not found), as expected - not blocking CI" else - echo "::error::The checked-in tcc.exe/libgc.a failed with the known aggregate count, but the direct crash.c compile error does NOT match the known broken-libc.dylib-symlink signature exactly (missing, abnormally terminated, or accompanied by another error) - this looks like a different, unexpected regression:" - echo "exit=$direct_code_checkedin: $direct_err_checkedin" + echo "::error::The checked-in tcc.exe/libgc.a failed with the known aggregate count, but the direct crash.c compile error does NOT match the known broken-libc.dylib-symlink signature exactly (missing, abnormally terminated, produced a runnable binary despite exit 0, or accompanied by another error) - this looks like a different, unexpected regression:" + echo "exit=$direct_code_checkedin produced_binary=$direct_produced_binary_checkedin: $direct_err_checkedin" exit 1 fi ;; From 95801731b9b9891da34bdd495575822b8634a86e Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Mon, 27 Jul 2026 08:10:54 -0400 Subject: [PATCH 30/30] ci: drop exit-0 rejection for the checked-in XFAIL - this ancient tcc's exit-code/output-file behavior is unreliable, not a meaningful signal Root-caused via a second real CI run: this ancient, checked-in v0.9.27 tcc.exe not only exits 0 while printing "tcc: error: library 'c' not found" (already observed once), it can ALSO leave behind an executable-permission output file in that same state - so neither "exit 0" nor "exit 0 AND a file exists at -o" reliably distinguishes "genuinely still broken as expected" from "secretly still working" for this specific 15+-year-old build's link-error handling. The exit-0 concern (Codex pullrequestreview-4783389496) is legitimate in principle - a CHANGED tcc.exe reporting false success is a real risk - but the outer `case "$output" in *"0 passed, 3 failed"*)` already independently confirms via run.sh's own compile+run cycle that all 3 shared tests genuinely failed; a real "tcc got un-broken" scenario would show up there as passing tests, not hidden in this one probe's exit code. This function's actual job is narrower: given the aggregate already failed, confirm the SPECIFIC cause via the error text, which two consecutive real CI runs now show is a reliable signal even when this old binary's exit code and file-production behavior for the same error are not. is_known_gc_init (the rebuilt/modern tcc's equivalent check) keeps its exit-0 rejection - the actively-maintained mob branch has shown no evidence of this quirk, unlike this fixed historical artifact. --- .github/workflows/build-and-test.yml | 54 ++++++++++++---------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index b301081..fcd9938 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -172,25 +172,11 @@ jobs: # error already root-caused earlier this session: tcc reports # "library 'c' not found" when its lib/libc.dylib symlink # target doesn't exist. - rm -f /tmp/crash_checkedin_probe set +e direct_err_checkedin=$(thirdparty/tcc/tcc.exe thirdparty/tccbin_tests/shared/crash.c \ -o /tmp/crash_checkedin_probe 2>&1) direct_code_checkedin=$? set -e - # This ancient, checked-in v0.9.27 tcc.exe has been observed - # (via a real CI run) to exit 0 while STILL printing "tcc: - # error: library 'c' not found" and NOT producing a runnable - # binary - a real quirk/bug of this specific old build, not a - # new regression. Whether the probe actually produced a - # runnable executable is a more direct, reliable signal of - # "did the compile really fail" than trusting this old - # binary's own exit code. - if [ -x /tmp/crash_checkedin_probe ]; then - direct_produced_binary_checkedin=1 - else - direct_produced_binary_checkedin=0 - fi # `other_checkedin=$(... | grep -v ...)` as a standalone # assignment (not a function called from an if-condition) is @@ -211,9 +197,8 @@ jobs: # is_known_gc_init/is_known_etext_end already work elsewhere # in this same file. is_known_checkedin_error() { - # $1 = captured stderr text, $2 = the probe's own exit code, - # $3 = 1 if the probe actually produced a runnable binary, - # 0 otherwise. + # $1 = captured stderr text, $2 = the probe's own exit code. + # # A regression could make tcc print the expected "library # 'c' not found" diagnostic and then crash/abort rather than # exiting cleanly - the earlier `|| true` discarded the exit @@ -224,18 +209,23 @@ jobs: if [ "$2" -gt 128 ]; then return 1 fi - # Exit 0 alone isn't disqualifying: this ancient checked-in - # tcc.exe has been observed to exit 0 while still printing - # the expected error and producing NO runnable binary (a - # real quirk/bug of this specific old build). What matters - # is whether a changed tcc.exe reports overall success AND - # actually produces a working executable despite the noise - # - that combination is the real problem the exit-0 check - # was meant to catch (Codex pullrequestreview-4783389496 on - # vlang/tccbin#74); exit 0 with no binary produced is not. - if [ "$2" -eq 0 ] && [ "$3" -eq 1 ]; then - return 1 - fi + # Deliberately NOT rejecting exit 0 here (unlike the + # rebuilt-archive equivalent check below): two separate + # real CI runs empirically showed this ancient, checked-in + # v0.9.27 tcc.exe prints "tcc: error: library 'c' not + # found" while exiting 0, and in one of those runs it even + # left behind an executable-permission /tmp file despite + # the error - this specific 15+-year-old build's exit-code/ + # output-file behavior for this error is simply unreliable + # as a signal, not evidence of "secretly still working". + # The outer `case "$output" in *"0 passed, 3 failed"*)` + # already independently confirms via run.sh's own + # compile+run cycle that all 3 shared tests genuinely + # failed - a real "tcc got fixed" scenario would show up + # there as passing tests, not in this probe's exit code. + # This function's remaining job is just to confirm the + # SPECIFIC cause (Codex pullrequestreview-4783389496 on + # vlang/tccbin#74). case "$1" in *"library 'c' not found"*) ;; *) return 1 ;; @@ -255,11 +245,11 @@ jobs: case "$output" in *"0 passed, 3 failed"*) - if is_known_checkedin_error "$direct_err_checkedin" "$direct_code_checkedin" "$direct_produced_binary_checkedin"; then + if is_known_checkedin_error "$direct_err_checkedin" "$direct_code_checkedin"; then echo "known XFAIL: the checked-in tcc.exe/libgc.a fail all 3 shared tests (stale binary, broken libc.dylib symlink - library 'c' not found), as expected - not blocking CI" else - echo "::error::The checked-in tcc.exe/libgc.a failed with the known aggregate count, but the direct crash.c compile error does NOT match the known broken-libc.dylib-symlink signature exactly (missing, abnormally terminated, produced a runnable binary despite exit 0, or accompanied by another error) - this looks like a different, unexpected regression:" - echo "exit=$direct_code_checkedin produced_binary=$direct_produced_binary_checkedin: $direct_err_checkedin" + echo "::error::The checked-in tcc.exe/libgc.a failed with the known aggregate count, but the direct crash.c compile error does NOT match the known broken-libc.dylib-symlink signature exactly (abnormally terminated, or accompanied by another error) - this looks like a different, unexpected regression:" + echo "exit=$direct_code_checkedin: $direct_err_checkedin" exit 1 fi ;;