Skip to content

ci(freebsd-amd64): flip libgc.a lane from XFAIL to blocking (etext/end fix validated) - #85

Open
quaesitor-scientiam wants to merge 2 commits into
vlang:thirdparty-freebsd-amd64from
quaesitor-scientiam:flip-freebsd-gc-lane-to-blocking
Open

ci(freebsd-amd64): flip libgc.a lane from XFAIL to blocking (etext/end fix validated)#85
quaesitor-scientiam wants to merge 2 commits into
vlang:thirdparty-freebsd-amd64from
quaesitor-scientiam:flip-freebsd-gc-lane-to-blocking

Conversation

@quaesitor-scientiam

@quaesitor-scientiam quaesitor-scientiam commented Jul 31, 2026

Copy link
Copy Markdown

Summary

tinycc 384614a ("tccelf: provide complete ELF boundary symbols") gives the plain BSD-style etext/edata/end names PROVIDE semantics on every ELF target. That is 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.

This flips the libgc.a lane from a signature-checked XFAIL to a normal blocking lane.

Note: da58264, referenced in earlier discussion, is not the fix — it's the RISC-V test-fixture follow-up ("tests2: keep linker boundary fixtures common"). The actual fix is 384614a.

Correction: this is NOT time-sensitive (my earlier claim was wrong)

An earlier revision of this description argued this was urgent because the monthly cron would publish a fixed tcc.exe on 2026-08-01 03:17 UTC and break CI. That was incorrect and I've removed it. vlang/v's update_tccbin.yml is disabled_manually at the GitHub level, so no scheduled run can fire at all:

gh api "repos/vlang/v/actions/workflows?per_page=100"   --jq '.workflows[] | select(.path|test("update_tccbin")) | {name, state}'
{"name":"Update tccbin","state":"disabled_manually"}

Its last schedule run was 2026-07-01. I had verified the cron expression, the PUBLISH logic, the absent BSD publish lock, and the skip-check — all accurate, and all irrelevant, because I never checked whether the workflow was enabled. Reading the YAML tells you what it would do, not whether it can run.

So there is no automatic recovery: merging this before a fixed tcc.exe is published would turn FreeBSD CI red and leave it red until someone manually publishes. Sequencing therefore has to be maintainer-driven, per the reviewer's plan below.

This strengthens the gate, it does not relax 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; no failure shape is tolerated. The blocking no-GC crash lane is unchanged.

Also adds a second blocking step 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. (Deliberately not an nm check — tcc emits no full .symtab by default and linker-PROVIDEd symbols needn't appear there at all; nm reported all three "absent" for a binary that had just linked and run correctly.)

Validation

Run on a real FreeBSD 15.1 VM before flipping, with this branch's exact libgc.a held byte-identical across both phases so tcc.exe was the only variable:

Phase Result
BEFORE — shipped tcc.exe (85ba3ae8, predates fix) fails unresolved reference to 'etext'/'end', and nothing else (exclusivity asserted)
Rebuild tinycc pinned + hash-verified at 2be0218b, asserted to contain 384614a; libgc.a byte-identical (cmp), tcc.exe confirmed replaced
AFTER — rebuilt tcc.exe 3 passed, 0 failed
Mechanism etext=0x400f0c edata=0x402110 end=0x402128 — provided and correctly ordered

The rebuild used vlang/v's own official thirdparty-freebsd-amd64_tcc.sh, i.e. exactly what the real publish pipeline runs. Full log: validation run (throwaway branch, since deleted).

No producer-side change is needed for FreeBSD — unlike macos-amd64, where libgc itself was never rebuilt. A plain tcc.exe rebuild from current mob is sufficient.

Expected CI on this PR

This PR's own run tests the currently shipped tcc.exe, which still predates the fix — so the now-blocking lane is expected to fail here until a rebuilt tcc.exe is published to this branch. That is the intended state, exactly as with the macos-amd64 gate conversion (#83), not a defect in this PR.

Because the cron is disabled, this must be merged as part of a coordinated publication so the red window stays bounded:

  1. Producer safeguards ready + update_tccbin.yml re-enabled.
  2. Publish a tcc.exe for this branch rebuilt from a tinycc containing 384614a (validated: 2be0218b works).
  3. Merge this PR immediately after, so the blocking lane meets an already-fixed package.

I have pull-only access to both repos, so steps 1-2 need a maintainer.

Test plan

  • YAML validated (yaml.safe_load); every step's script syntax-checked under POSIX sh -n (the VM uses sh, not bash).
  • Before/after validated on a real FreeBSD 15.1 VM with libgc.a held byte-identical.
  • Boundary-symbol probe compiles, links, runs, and reports a sanely ordered range.
  • Probe hardened per review: compares uintptr_t values (relational comparison of pointers into distinct objects is UB) and asserts the full etext <= edata <= end chain, non-strict so an empty .data/.bss isn't a spurious failure. Re-validated on a real FreeBSD VM against a fixed tcc.exe.
  • Confirmed the pre-existing Prettier non-compliance of this file is unrelated to this change (the unmodified file reports identically) and that this repo has no lint workflow — so no reformatting noise added here.
  • Confirm this PR's run fails only at the now-blocking GC lane, for the expected pre-publish reason.
  • After the rebuilt tcc.exe is published, confirm this branch's CI is fully green.

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>
@GGRei

GGRei commented Jul 31, 2026

Copy link
Copy Markdown

Technically, your PR is the right follow-up, but we will not merge it yet. update_tccbin.yml is currently disabled, so the August cron cannot publish anything, and the checked-in FreeBSD package still predates TinyCC 384614a. Merging now would knowingly turn FreeBSD CI red with no automatic recovery. Please first harden the boundary probe by comparing uintptr_t values and asserting etext <= edata <= end. Once that is done and our producer safeguards are ready, we will merge #85 as part of the coordinated FreeBSD publication so the red window remains bounded.

…ata <= end

Per review on vlang#85.

- Compare as uintptr_t rather than as pointers: relational comparison of
  pointers into distinct objects is undefined behaviour in C, so the
  previous pointer-level check was not one a compiler is obliged to
  evaluate the way it reads. Integer comparison of the converted
  addresses is well defined.
- Assert the FULL ordering etext <= edata <= end rather than only
  etext < end. 384614a is about boundary symbols being finalized from the
  ordered loadable sections, so provided-but-mis-ordered is a distinct
  failure mode, and only the complete chain catches a middle symbol
  landing out of place.
- Non-strict (<=) deliberately: an empty .data or .bss makes adjacent
  boundaries legitimately equal, and a strict < would fail such a build
  for no good reason.

Co-Authored-By: WOZCODE <contact@withwoz.com>
@quaesitor-scientiam

Copy link
Copy Markdown
Author

Thanks — and you're right about the cron. I've corrected the PR description.

On update_tccbin.yml being disabled: confirmed, state: "disabled_manually", last schedule run 2026-07-01. My urgency claim was wrong. I'd verified the cron expression, the PUBLISH logic, the absent BSD publish lock, and the skip-check — all accurate, and all irrelevant, because I never checked whether the workflow was enabled at all. Reading the YAML tells you what it would do, not whether it can run. Agreed that merging now would knowingly turn FreeBSD CI red with no automatic recovery, and the description no longer recommends that.

Probe hardened as requested in 294c90f:

  • Compares uintptr_t values rather than pointers — relational comparison of pointers into distinct objects is UB, so the previous pointer-level check wasn't one a compiler is obliged to evaluate as written.
  • Asserts the full etext <= edata <= end chain rather than only etext < end, so a mis-ordered middle symbol is caught and not just a missing one — which matches what 384614a actually changed (boundary symbols finalized from the ordered loadable sections).
  • Non-strict <= deliberately: an empty .data or .bss makes adjacent boundaries legitimately equal, and a strict < would fail such a build for no good reason.
uintptr_t text_end = (uintptr_t)etext;
uintptr_t data_end = (uintptr_t)edata;
uintptr_t bss_end  = (uintptr_t)end;

printf("etext=0x%" PRIxPTR " edata=0x%" PRIxPTR " end=0x%" PRIxPTR "\n",
       text_end, data_end, bss_end);

if (!(text_end <= data_end && data_end <= bss_end)) {
    fprintf(stderr, "bad boundary range: expected etext <= edata <= end\n");
    return 1;
}

Re-validated on a real FreeBSD 15.1 VM rather than assumed. gcc -std=c99 -Wall -Wextra -pedantic accepting it doesn't prove tcc handles <inttypes.h>'s PRIxPTR macro concatenation or uintptr_t — and that failure mode would only surface after publication, which is the worst time to discover it. Full before/after run with this branch's exact libgc.a held byte-identical:

BEFORE confirmed: shipped tcc.exe fails on unresolved etext/end, and on nothing else
confirmed: HEAD is exactly 2be0218be4461f9e453cbdcb98f81f41a5ae8bed, and it contains 384614a945b68b78343a25414eb74c26947ae542
confirmed: libgc.a unchanged, tcc.exe replaced - tcc.exe is the only variable between phases
3 passed, 0 failed
etext=0x400f3c edata=0x402140 end=0x402158
confirmed: the linker provides etext/edata/end, and the advertised range is ordered sanely

The hardened probe compiles, links and runs under tcc, and the ordering assertion holds on a real build. (Throwaway validation branch, since deleted.)

No merge request from me — this sits until your producer safeguards are ready, to be merged as part of the coordinated publication so the red window stays bounded. Happy to adjust anything else in the meantime.

One note for whenever that publication happens: da58264 (cited earlier) is the RISC-V test-fixture follow-up, not the fix — the commit that needs to be in the published tcc.exe is 384614a. 2be0218b is the mob HEAD I validated against and contains it.

@GGRei

GGRei commented Jul 31, 2026

Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 294c90f95d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants