Skip to content

CI: native-roots-rs4gc red on main (ELF) — provider fixture aborts with "stdlib provider is bound to a different runtime image" #8156

Closed

Description

@proggeramlug

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_init as 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).

Activity

  1. proggeramlug commented on Aug 15, 2026

    @proggeramlug
    ContributorAuthor

    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"
    #8127 @ cb7b49abf T global OK
    drop the interception entirely T global OK

    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).

  2. proggeramlug commented on Aug 16, 2026

    @proggeramlug
    ContributorAuthor

    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.

    Closing.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions