Skip to content

Commit 7bc875d

Browse files
ci: flip the libgc.a lane from signature-checked XFAIL to blocking
tinycc 384614a ("tccelf: provide complete ELF boundary symbols") gives the plain BSD-style etext/edata/end names PROVIDE semantics on every ELF target, fixing the root cause of this platform's GC link failure: tcc previously defined only the glibc-style _etext/_edata/_end, never the plain names BDWGC's FreeBSD data-segment scan references directly. The lane asserted that gc_alloc.c/hello.c MUST still fail on unresolved etext/end - correct while the bug was live, but it hard-fails the moment a fixed tcc.exe is published here, via its own "unexpected full pass" guard. That is imminent: update_tccbin.yml's BSD job publishes on the monthly cron with PUBLISH=true and no lock, and its skip-check will not skip (shipped 85ba3ae8 != current mob), so the next scheduled run republishes this branch with a fixed tcc.exe and breaks CI for the opposite reason. This STRENGTHENS the gate rather than relaxing it: a full pass of all three shared tests is now the required baseline, where before only one very specific failure shape was accepted. Nothing is downgraded to a warning and no failure shape is tolerated. Also adds a blocking mechanism check that references etext/edata/end directly from C, so a future tcc.exe that satisfies the suite while regressing or mis-ordering the boundary symbols is still caught. Validated on a real FreeBSD 15.1 VM before flipping, with this branch's EXACT libgc.a held byte-identical: the shipped tcc.exe (85ba3ae8) fails on unresolved etext/end and nothing else, while a tcc.exe rebuilt from tinycc 2be0218b (which contains 384614a) reports "3 passed, 0 failed" and links the boundary probe with a sanely ordered range. tcc.exe was the only variable between the two phases. Co-Authored-By: WOZCODE <contact@withwoz.com>
1 parent 1289f0a commit 7bc875d

1 file changed

Lines changed: 92 additions & 152 deletions

File tree

.github/workflows/build-and-test.yml

Lines changed: 92 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -103,25 +103,26 @@ jobs:
103103
104104
chmod +x thirdparty/tcc/tcc.exe
105105
106-
# tcc on FreeBSD cannot currently link the bundled libgc.a: its
107-
# linker only ever defines the glibc-style _etext/_edata/_end
108-
# (see tccelf.c's tcc_add_linker_symbols), never the plain BSD-
109-
# style etext/edata/end that BDWGC's FreeBSD data-segment-scanning
110-
# code references directly - so any GC-dependent program fails
111-
# with "undefined symbol 'etext'"/"'end'" (or, on the tcc build
112-
# currently on this branch, "unresolved reference to 'etext'"/
113-
# "'end'" - same underlying bug, tcc has used both diagnostic
114-
# wordings for an unresolved-symbol linker error across versions).
115-
# Root-caused and a fix
116-
# verified against a real FreeBSD VM (patched tcc successfully
117-
# builds+runs shared/hello.c against this same libgc.a); the fix
118-
# is being submitted upstream to tinycc-devel but isn't in any
119-
# shipped tcc.exe yet, so it can't be relied on here.
106+
# HISTORY: tcc on FreeBSD used to be unable to link the bundled
107+
# libgc.a at all - its linker only ever defined the glibc-style
108+
# _etext/_edata/_end (see tccelf.c's tcc_add_linker_symbols), never
109+
# the plain BSD-style etext/edata/end that BDWGC's FreeBSD
110+
# data-segment-scanning code references directly, so any
111+
# GC-dependent program failed with "undefined symbol 'etext'"/
112+
# "'end'" (later builds reworded this to "unresolved reference
113+
# to ..."). That is FIXED upstream as of tinycc 384614a, "tccelf:
114+
# provide complete ELF boundary symbols", which gives the plain
115+
# etext/edata/end names PROVIDE semantics on every ELF target.
116+
#
117+
# The GC lane below is therefore a normal BLOCKING lane now, not an
118+
# XFAIL - see the comment above it.
120119
#
121120
# crash.c doesn't touch the GC at all, so it's checked directly
122121
# (bypassing run.sh, which would otherwise apply the same GC
123-
# flags to every test) as the real, blocking regression check for
124-
# this platform's tcc/libtcc1.a/crt pairing.
122+
# flags to every test) as an independent, blocking regression check
123+
# for this platform's tcc/libtcc1.a/crt pairing - it still catches a
124+
# broken toolchain even if the GC lane were to fail for its own
125+
# unrelated reasons.
125126
- name: run shared conformance tests (crash only, no GC - blocking)
126127
shell: cpa.sh {0}
127128
run: |
@@ -137,8 +138,7 @@ jobs:
137138
# Any nonzero exit isn't enough: an exec-format error, a
138139
# missing dynamic loader, or an unrelated miscompilation that
139140
# just exit(1)s would also satisfy that and silently mask a
140-
# real regression, since the GC-backed suite below is
141-
# non-blocking. crash.c's null-pointer dereference is
141+
# real regression. crash.c's null-pointer dereference is
142142
# expected to be killed by SIGSEGV specifically, which a
143143
# POSIX shell reports as exit code 128+11=139 - verify that
144144
# exact signal-terminated signature, not just "nonzero".
@@ -153,13 +153,15 @@ jobs:
153153
# segfault immediately (before ever reaching the intentional
154154
# null-pointer dereference), this check would still see exit
155155
# 139 and wrongly call it "expected" (Codex pullrequestreview-
156-
# 4781468264 on vlang/tccbin#75). The GC-backed lane below
157-
# can't provide this positive check either, since it's
158-
# expected to fail at link time. Compile and run a trivial,
159-
# non-crashing program with the same no-GC flags, requiring it
160-
# to actually complete and exit 0 - proving the toolchain can
161-
# still produce a genuinely working binary, not just one that
162-
# crashes regardless of source.
156+
# 4781468264 on vlang/tccbin#75). Keep this positive check
157+
# independent of the GC lane below rather than relying on it:
158+
# this lane's whole purpose is to stay meaningful on its own,
159+
# covering the plain no-GC toolchain path that a GC-linked test
160+
# never exercises. Compile and run a trivial, non-crashing
161+
# program with the same no-GC flags, requiring it to actually
162+
# complete and exit 0 - proving the toolchain can still produce
163+
# a genuinely working binary, not just one that crashes
164+
# regardless of source.
163165
cat > /tmp/nogc_trivial.c <<'TRIVIALEOF'
164166
int main(void) { return 0; }
165167
TRIVIALEOF
@@ -174,137 +176,75 @@ jobs:
174176
fi
175177
echo "trivial no-GC success check passed (exit=0)"
176178
177-
# continue-on-error alone would also swallow an unrelated failure
178-
# (a corrupted libgc.a, a bad checkout, run.sh going missing) as if
179-
# it were the known etext/end failure, and the blocking crash-only
180-
# step above never references libgc.a at all - so a real
181-
# regression in the archive itself could pass CI unnoticed (Codex
182-
# pullrequestreview-4780048339 on vlang/tccbin#75, line 107).
183-
# Assert the exact known failure signature instead, mirroring
184-
# macos-arm64's own libgc.a XFAIL lane: any other failure shape
185-
# (or an unexpected full pass, meaning the etext/end fix landed)
186-
# fails the job for real.
187-
- name: run shared conformance tests (libgc.a - XFAIL until the etext/end tcc fix ships)
179+
# BLOCKING as of tinycc 384614a ("tccelf: provide complete ELF
180+
# boundary symbols"), which is what this branch's tcc.exe must now
181+
# be built from or later. This lane was previously a
182+
# signature-checked XFAIL asserting that gc_alloc.c/hello.c MUST
183+
# still fail on unresolved etext/end - correct while the bug was
184+
# live, but it would hard-fail the moment a fixed tcc.exe is
185+
# published here (its own "unexpected full pass" guard fired on
186+
# exactly that), breaking CI for the opposite reason.
187+
#
188+
# This STRENGTHENS the gate rather than relaxing it: a full pass of
189+
# all three shared tests is now the required baseline, where before
190+
# only a very specific failure was accepted. Nothing here is
191+
# downgraded to a warning, and no failure shape is tolerated.
192+
#
193+
# Verified on a real FreeBSD 15.1 VM before flipping: with this
194+
# branch's EXACT libgc.a held byte-identical, the shipped tcc.exe
195+
# (85ba3ae8, predating the fix) fails on unresolved etext/end and
196+
# nothing else, while a tcc.exe rebuilt from tinycc 2be0218b (which
197+
# contains 384614a) reports "3 passed, 0 failed". tcc.exe was the
198+
# only variable between the two.
199+
- name: run shared conformance tests (libgc.a - blocking)
188200
shell: cpa.sh {0}
189201
run: |
190-
set +e
191-
output=$(bash vsrc/thirdparty/tccbin_tests/run.sh "$PWD/thirdparty/tcc/tcc.exe" freebsd -- \
202+
set -eu
203+
bash vsrc/thirdparty/tccbin_tests/run.sh "$PWD/thirdparty/tcc/tcc.exe" freebsd -- \
192204
-DGC_BUILTIN_ATOMIC=1 -DBUS_PAGE_FAULT=T_PAGEFLT -DALL_INTERIOR_POINTERS=1 \
193205
-I "$PWD/vsrc/thirdparty/libgc/include" \
194206
"$PWD/thirdparty/tcc/lib/libgc.a" \
195-
-lpthread 2>&1)
196-
code=$?
197-
set -e
198-
echo "$output"
199-
200-
if [ "$code" -eq 0 ]; then
201-
echo "expected the known etext/end unresolved-symbol failure, but this lane fully passed - the upstream tcc fix must have landed; remove this XFAIL special-casing and fold it back into a single blocking lane." >&2
202-
exit 1
203-
fi
207+
-lpthread
204208
205-
# The pinned run.sh only ever prints "(compile error)" with no
206-
# detail, so the summary-line match above can't tell "the known
207-
# etext/end bug" apart from a DIFFERENT static-link regression
208-
# that happens to fail the same two tests - e.g. a PR that
209-
# replaces libgc.a with a valid-but-incomplete archive lacking
210-
# the GC objects entirely: crash.c still compiles (it never
211-
# references GC symbols) while gc_alloc/hello both report
212-
# "compile error", matching this same summary shape for an
213-
# unrelated reason (Codex pullrequestreview-4780219278 on
214-
# vlang/tccbin#75). Compile BOTH failing tests directly to
215-
# capture their real tcc stderr and assert each is specifically
216-
# the known etext/end unresolved-symbol error - checking only
217-
# one of the two (e.g. hello.c) would let an unrelated
218-
# regression isolated to the OTHER test (gc_alloc.c) hide
219-
# behind this same summary shape and pass unnoticed (Codex
220-
# pullrequestreview-4780815399 on vlang/tccbin#75).
221-
set +e
222-
direct_err_hello=$(thirdparty/tcc/tcc.exe vsrc/thirdparty/tccbin_tests/shared/hello.c \
223-
-DGC_BUILTIN_ATOMIC=1 -DBUS_PAGE_FAULT=T_PAGEFLT -DALL_INTERIOR_POINTERS=1 \
224-
-I vsrc/thirdparty/libgc/include \
225-
thirdparty/tcc/lib/libgc.a \
226-
-lpthread \
227-
-o /tmp/hello_xfail_probe 2>&1)
228-
direct_code_hello=$?
229-
direct_err_gc_alloc=$(thirdparty/tcc/tcc.exe vsrc/thirdparty/tccbin_tests/shared/gc_alloc.c \
230-
-DGC_BUILTIN_ATOMIC=1 -DBUS_PAGE_FAULT=T_PAGEFLT -DALL_INTERIOR_POINTERS=1 \
231-
-I vsrc/thirdparty/libgc/include \
232-
thirdparty/tcc/lib/libgc.a \
233-
-lpthread \
234-
-o /tmp/gc_alloc_xfail_probe 2>&1)
235-
direct_code_gc_alloc=$?
236-
set -e
237-
238-
# Unlike macos-amd64's whole-archive-unparseable bug, this
239-
# FreeBSD bug is narrow: tcc's linker only ever fails to define
240-
# the two plain BSD-style symbols (etext, end) that BDWGC
241-
# references directly - nothing else in the archive is
242-
# affected. So an exact match here is the correct check: reject
243-
# if any unresolved-reference line names a symbol OTHER than
244-
# etext/end, since that would mean a genuinely different,
245-
# additional regression rode along with the known bug (Codex
246-
# pullrequestreview-4780907186 on vlang/tccbin#75).
247-
is_known_etext_end() {
248-
# $1 = captured stderr text, $2 = the probe's own exit code.
249-
# A regression could make tcc print the expected etext/end
250-
# diagnostic and then crash/abort rather than exiting
251-
# cleanly with its normal compile-error status - the earlier
252-
# `|| true` discarded that exit code entirely, so a shell
253-
# "Segmentation fault" or similar abnormal termination
254-
# alongside the expected text would still pass (Codex
255-
# pullrequestreview-4781865117 on vlang/tccbin#75). A POSIX
256-
# shell reports a signal-terminated process as exit code
257-
# 128+signal; reject those instead of treating any nonzero
258-
# exit as "the expected compile-error status". Also reject
259-
# exit 0: a changed tcc.exe could start printing this same
260-
# diagnostic as noise while still reporting overall success
261-
# - the text alone isn't proof the compile actually failed
262-
# (Codex pullrequestreview-4783389496 on the sibling
263-
# macos-amd64 workflow, vlang/tccbin#74, same class of gap
264-
# here).
265-
if [ "$2" -eq 0 ] || [ "$2" -gt 128 ]; then
266-
return 1
267-
fi
268-
# Run 30421217468 (2026-07-29) is a confirmed matcher false
269-
# negative, not a real regression: this branch's tcc now
270-
# reports "unresolved reference to 'etext'"/"'end'" instead of
271-
# the "undefined symbol '...'" wording this check originally
272-
# matched - same known bug, different tcc diagnostic wording
273-
# (tcc has used both phrasings for an unresolved-symbol linker
274-
# error across versions/builds). Accept either wording rather
275-
# than replacing one with the other, so a differently-built
276-
# tcc.exe landing here later isn't a second false negative.
277-
case "$1" in
278-
*"undefined symbol 'etext'"*|*"undefined symbol 'end'"*|*"unresolved reference to 'etext'"*|*"unresolved reference to 'end'"*) ;;
279-
*) return 1 ;;
280-
esac
281-
# Filtering only the known-wording lines and checking their
282-
# NAMES misses a wholly DIFFERENT kind of tcc error (invalid
283-
# object, relocation error, etc.) riding alongside the known
284-
# one - such a line simply wouldn't match either wording
285-
# pattern at extraction, so `other` would stay empty and this
286-
# would wrongly accept the XFAIL (Codex pullrequestreview-
287-
# 4781468264 on vlang/tccbin#75). Match on tcc's actual
288-
# error-line prefix instead, so ANY tcc error line that isn't
289-
# specifically etext/end (under either wording) gets caught,
290-
# not just a differently-named undefined symbol.
291-
other=$(printf '%s\n' "$1" | grep -E '^tcc: error:' | grep -v -E "undefined symbol '(etext|end)'|unresolved reference to '(etext|end)'")
292-
[ -z "$other" ]
209+
# Positive proof of the MECHANISM, not just the outcome. The lane
210+
# above passing means BDWGC linked, but it does not by itself prove
211+
# WHY - a future tcc.exe could conceivably satisfy the suite while
212+
# regressing the boundary symbols BDWGC's FreeBSD data-segment scan
213+
# depends on, or provide them with a nonsensical range. Reference
214+
# all three directly from C: if the linker fails to provide them,
215+
# this simply does not link, which is precisely the original bug.
216+
#
217+
# Deliberately NOT an `nm` symbol-table check - that was tried while
218+
# validating this flip and is the wrong instrument: tcc emits no
219+
# full .symtab by default, and linker-PROVIDEd symbols need not
220+
# appear in the output binary's symbol table at all, so `nm`
221+
# reported all three "absent" for a binary that had just linked and
222+
# run correctly.
223+
- name: verify the linker provides etext/edata/end (blocking)
224+
shell: cpa.sh {0}
225+
if: ${{ !cancelled() }}
226+
run: |
227+
set -eu
228+
cat > /tmp/boundary_probe.c <<'PROBEEOF'
229+
#include <stdio.h>
230+
/* The plain BSD-style names BDWGC's FreeBSD data-segment scan
231+
references, and that tcc did not provide before 384614a. */
232+
extern char etext[], edata[], end[];
233+
int main(void) {
234+
printf("etext=%p edata=%p end=%p\n",
235+
(void *)etext, (void *)edata, (void *)end);
236+
/* Sanity-check the advertised range, not merely the
237+
symbols' existence: 384614a is specifically about
238+
boundary symbols being finalized from the ordered
239+
loadable sections, so provided-but-wrong is a distinct
240+
failure mode from not-provided. */
241+
if (!((void *)etext < (void *)end)) {
242+
fprintf(stderr, "bad boundary range: etext is not below end\n");
243+
return 1;
244+
}
245+
return 0;
293246
}
294-
295-
case "$output" in
296-
*"PASS crash"*"FAIL gc_alloc (compile error)"*"FAIL hello (compile error)"*)
297-
if is_known_etext_end "$direct_err_hello" "$direct_code_hello" && is_known_etext_end "$direct_err_gc_alloc" "$direct_code_gc_alloc"; then
298-
echo "known XFAIL: etext/end still unresolved on FreeBSD tcc for both gc_alloc.c and hello.c, as expected - not blocking CI"
299-
else
300-
echo "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 etext/end symbol (or exited abnormally) - this looks like a different, unexpected regression:" >&2
301-
echo "hello.c: exit=$direct_code_hello: $direct_err_hello" >&2
302-
echo "gc_alloc.c: exit=$direct_code_gc_alloc: $direct_err_gc_alloc" >&2
303-
exit 1
304-
fi
305-
;;
306-
*)
307-
echo "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." >&2
308-
exit 1
309-
;;
310-
esac
247+
PROBEEOF
248+
thirdparty/tcc/tcc.exe /tmp/boundary_probe.c -o /tmp/boundary_probe
249+
/tmp/boundary_probe
250+
echo "confirmed: the linker provides etext/edata/end, and the advertised range is ordered sanely"

0 commit comments

Comments
 (0)