You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
CI: native-roots-rs4gc red on main (ELF) — provider fixture aborts with "stdlib provider is bound to a different runtime image" #8156
gc-native-roots / native-roots-rs4gc has been failing on main (ELF arms) since at least 2026-08-14, and consequently on every PR branched from it.
Symptom — the issue_8075_provider_gc fixture host aborts before it runs any handler:
Error: "stdlib provider is bound to a different runtime image"
That check is tests/fixtures/issue_8075_provider_gc/host.rs: the stdlib provider exports a probe returning the address of js_gc_initas resolved inside the stdlib image, and the host compares it with the address it resolved from the runtime image. A mismatch means the stdlib provider bound its own bundled copy of perry-runtime instead of the shared one — i.e. the archive trim that is supposed to remove the bundled runtime members no longer removes all of them (or the link order changed so the bundled copy wins).
Affected runs (main):
83b6b8c69 — failure
506f4ab11 — failure
5c27d1ad9 — success (last known good)
So the regression landed between 5c27d1ad9 and 506f4ab11.
Why it matters: this is the gate for #8075/#8081's whole subject — native GC roots when Perry runs behind separately loaded runtime and stdlib providers. While it aborts at the provider handshake it never reaches the root-map assertions, so it cannot fail for the reason it exists (CLAUDE.md's fourth "gate cannot fail" mode: the gate runs, its subject never does).
Bisect range confirmed and the mechanism measured on real ELF (Linux/aarch64 container, this repo's actual stdlib-linker.sh driving a cargo build --profile provider of a stand-in provider cdylib, then host.rs's own dlopen/probe() check).
5c27d1ad9..506f4ab11 contains exactly one commit — #8089 — and it causes the break rather than exposing one. What it added on the ELF side is a hand-written version script ending in local: *.
The provider statically links the perry-runtime rlib and loads the runtime .so, so it carries its own js_gc_init. Before #8089 the rustc-generated version script was passed through untouched, and rustc lists every #[no_mangle] symbol in the crate graph — upstream rlibs included — in its global: section. The bundled js_gc_init therefore stayed a global dynsym entry and was preempted at load by the runtime provider, which the host dlopens first with RTLD_GLOBAL. local: * demotes it to a local t, and a local symbol is not preemptible, so the probe returns the provider's own copy. The check is correct; the linkage is what broke.
Measured, same harness, four linker-script variants:
stdlib-linker.sh from
js_gc_init in stdlib .dynsym
probe
5c27d1ad9
T global
OK
origin/main
absent — local t
Error: "stdlib provider is bound to a different runtime image"
Mach-O passes because its branch re-exports the runtime's whole symbol table and links -flat_namespace -interposable; Windows never runs the step. So the asymmetry, not the platform, is the reason this reached main.
Fix is in flight as #8127 (draft). Its current head does fix it — verified above, not inferred. Detailed numbers, plus the case for the smaller variant (pass rustc's script through instead of replacing it: 19 exports and the last-good surface, versus 894 exports and 871 newly-preemptible std/panic/alloc symbols shared with the runtime image), are in #8127 (comment).
Fixed by #8127 (b39e761a9), verified an ancestor of 3be2016c1.
tests/fixtures/issue_8075_provider_gc/stdlib-linker.sh now parses -Wl,--version-script= (line 55) and re-emits the script with global: and no local: * — with an inline comment naming the exact preemption mechanism the bisect in this issue established: the provider carries its own js_gc_init, and local: * bound it internally so the host's probe resolved a second runtime image instead of the shared one ("stdlib provider is bound to a different runtime image").
The subject the gate exists for — loaded-provider root maps — is reachable again. The four-variant table posted in this issue is the acceptance evidence.
Activity
proggeramlug commentedon Aug 15, 2026
Bisect range confirmed and the mechanism measured on real ELF (Linux/aarch64 container, this repo's actual
stdlib-linker.shdriving acargo build --profile providerof a stand-in provider cdylib, thenhost.rs's owndlopen/probe()check).5c27d1ad9..506f4ab11contains exactly one commit — #8089 — and it causes the break rather than exposing one. What it added on the ELF side is a hand-written version script ending inlocal: *.The provider statically links the
perry-runtimerlib and loads the runtime.so, so it carries its ownjs_gc_init. Before #8089 the rustc-generated version script was passed through untouched, and rustc lists every#[no_mangle]symbol in the crate graph — upstream rlibs included — in itsglobal:section. The bundledjs_gc_inittherefore stayed a global dynsym entry and was preempted at load by the runtime provider, which the hostdlopens first withRTLD_GLOBAL.local: *demotes it to a localt, and a local symbol is not preemptible, so the probe returns the provider's own copy. The check is correct; the linkage is what broke.Measured, same harness, four linker-script variants:
stdlib-linker.shfromjs_gc_initin stdlib.dynsym5c27d1ad9Tglobalorigin/maintError: "stdlib provider is bound to a different runtime image"cb7b49abfTglobalTglobalMach-O passes because its branch re-exports the runtime's whole symbol table and links
-flat_namespace -interposable; Windows never runs the step. So the asymmetry, not the platform, is the reason this reachedmain.Fix is in flight as #8127 (draft). Its current head does fix it — verified above, not inferred. Detailed numbers, plus the case for the smaller variant (pass rustc's script through instead of replacing it: 19 exports and the last-good surface, versus 894 exports and 871 newly-preemptible std/panic/alloc symbols shared with the runtime image), are in #8127 (comment).
proggeramlug commentedon Aug 16, 2026
Fixed by #8127 (
b39e761a9), verified an ancestor of3be2016c1.tests/fixtures/issue_8075_provider_gc/stdlib-linker.shnow parses-Wl,--version-script=(line 55) and re-emits the script withglobal:and nolocal: *— with an inline comment naming the exact preemption mechanism the bisect in this issue established: the provider carries its ownjs_gc_init, andlocal: *bound it internally so the host's probe resolved a second runtime image instead of the shared one ("stdlib provider is bound to a different runtime image").The subject the gate exists for — loaded-provider root maps — is reachable again. The four-variant table posted in this issue is the acceptance evidence.
Closing.