Skip to content

POLL_CAPABLE_RUNTIME audit cannot see macro-generated exports: js_string_replace_regex_fn reads as missing #10389

Closed

Description

@proggeramlug

scripts/ GC root-dominance audit fails on main with:

error: POLL_CAPABLE_RUNTIME entries that name no runtime symbol:
=== POLL_CAPABLE_RUNTIME: 147 entries vs 4099 exported js_* symbols

The two entries are js_string_replace_regex_fn and js_string_replace_all_regex_fn.

The symbols are real; the scanner is blind

Both are exported. The perex-adoption commit 178a2bb367 turned them into macro-generated exports:

regex_value!(js_string_replace_regex_fn, false)

runtime_symbols()'s _EXTERN_C_FN_RE is a literal source-text regex, so it cannot see a symbol that a macro produces. The entries are correct, the audit's view of the tree is not.

This is the same class as a bug the auditor's own comments already record, in its words: "The name was never wrong; the scanner was."

Why this matters more than a red gate

The gate's advice when an entry names no symbol is to replace the entry, not delete it, and that advice is right. Deleting these two would remove a genuine poll-capable declaration for a function that really is poll-capable — converting a scanner blind spot into a silent hole in the GC root-dominance analysis. A future reader would have no way to tell the entry had ever existed.

So the fix belongs in the scanner: teach runtime_symbols() to expand the macro forms that now generate js_* exports, regex_value! first. Anything else that generates exports through a macro is in the same position and should be enumerated at the same time.

How this was established

Found while triaging PR #10354 (the turnloop migration), where the initial hypothesis was that a deleted crate had left stale entries. That hypothesis was wrong, and three independent checks disproved it:

  1. POLL_CAPABLE_RUNTIME contains zero js_fetch_* entries (147 total).
  2. SYMBOL_ROOTS covers only perry-runtime/src and perry-stdlib/src, so the crate in question was never in scope.
  3. A pure origin/main tree extracted with git archive fails the audit with the same two entries.

That third check is the one that settles it: this is a main failure, unrelated to any branch.

Activity

  1. proggeramlug commented on Sep 22, 2026

    @proggeramlug
    ContributorAuthor

    Reconfirmed during measurement/gate audit of #10960 on 2026-09-22. This now blocks both gc-root-dominance and gc-root-dominance-statepoints before either compiler build or IR corpus runs.

    Evidence from the API (actual conclusion, not gh pr checks rendering):

    js_ratelimit_new_from_options
    js_string_replace_all_regex_fn
    js_string_replace_regex_fn
    

    The assertion is POLL_CAPABLE_RUNTIME <= runtime_symbols(), after a minimum population of 500 exports. runtime_symbols() scans literal source under crates/perry-runtime/src and crates/perry-stdlib/src using extern\s+"C(?:-unwind)?"\s+fn\s+(js_\w+). This is a source inventory assertion, not an observed root-dominance violation.

    There are two mechanisms, requiring different fixes:

    1. The regex pair are real exports generated by regex_value! at crates/perry-runtime/src/regex/perex_replace_compat.rs:89–90 (macro body at line 66). Perex adoption 178a2bb36 introduced this form. The literal-source scanner misses fn $name. The names remain declared in crates/perry-codegen/src/runtime_decls/strings_part2.rs:404–405 and called in crates/perry-runtime/src/regex/replace_fn.rs:641,663. Keep these poll-capable entries. Repair export enumeration generally, with macro-generated export coverage; do not whitelist two symbol strings or assume every macro!(js_...) invocation exports a function. Also audit the related runtime-body/call-graph scanner so recognizing a name does not conceal missing reach edges.
    2. js_ratelimit_new_from_options really was removed: dcb8a760a8a5abd39cf4b8187ff60b966b1aa05b deleted crates/perry-stdlib/src/ratelimit.rs, its codegen declarations/dispatch and native binding. git grep js_ratelimit_new_from_options a022cf2e4 -- crates scripts finds only the audit entry. That entry is stale and may be removed with this provenance; it is not evidence that the regex pair should be removed.

    Acceptance: restore both real exports to the inventory; remove only the proved deleted export; run alloc, poll-capable, and poll-reach audits with population floors; plant a nonexistent export and prove the audit exits 2 naming it, then remove it; sabotage macro-export recognition and prove a regression test fails. Finally run both full GC jobs to completion on the same SHA and inspect every step. Clearing this preflight alone must not be reported as green dominance corpora.

    The cheap symbol-existence audit currently lives behind the extended-test label on PRs; move its repaired preflight into the always-on lint path (next to the existing poll-reach audit), retain the scheduled/full corpus runs, and verify a first green main run before promoting any additional required gate. This prevents the next unrelated labelled PR from inheriting a hidden broken inventory.

    Task status: repair deferred to this issue; no audit entry was silenced. Existing main-health tracker: #9925.

    Additional independent proof: a clean Linux release build of main a022cf2e4 completed successfully (compiler plus both static-wrapper packages). nm -g --defined-only target/release/libperry_runtime.a finds exactly both regex exports as defined T symbols. Runtime archive SHA-256: 19cc952e11efed942b0719aefdabe5849ab234e690d91378dc92ca68d6d913a9. This distinguishes a scanner blind spot from a declaration that exists in source but is absent from the built library.

    Separate #10960 lint inspection found real failures beyond the known benchmark fingerprint: missing changeset and PASS1_MARKED's non_moving_snapshot source pin changed for gc/policy.rs in the runtime-holder audit (lint job). Those require their own resolution; they are not evidence for blaming this inherited GC preflight failure on #10960.

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