Skip to content

Commit 07c8040

Browse files
proggeramlugRalph Küpper
andauthored
fix(gc): the root-dominance symbol scan must accept the C-unwind ABI (#8207)
* fix(gc): the root-dominance symbol scan must accept the C-unwind ABI `gc-root-dominance` and `gc-root-dominance-statepoints` are red on main: error: POLL_CAPABLE_RUNTIME entries that name no runtime symbol: js_closure_call2 `js_closure_call2` is a real, exported runtime symbol. What changed is its ABI string: it is declared `extern "C-unwind"`, and `runtime_symbols` scans for `extern "C" fn js_*` only. So the audit concluded the entry named nothing. The audit's own message warns against the wrong repair — "do not just delete it, or the audit goes green and the hole stays" — and that applies here: the entry is correct, the scanner is blind. `"C-unwind"` is a distinct ABI string but the SAME exported C symbol, and the runtime uses it for every entry point a JS exception may unwind through. Today that is 18 symbols, and they are not incidental: `js_throw`, the entire `js_native_call_method*` dispatch family, the `js_typed_feedback_native_call_*` paths, and `js_closure_call2` — precisely the allocating, poll-capable calls this analysis exists to reason about. Every consumer of `runtime_symbols` was under-counting them, so the blindness was never limited to the one phantom that made it visible. Accept both spellings. `runtime_symbols` goes from 3803 to 3821 exported symbols, and the phantom clears. All five audits the workflow runs pass: `--self-test`, `--audit-alloc-re` (71 alternatives, all matched), `--audit-poll-capable` (137 entries, all matched), `--audit-immovable-sources`, and `--audit-poll-reach` (1622 symbols with an intra-runtime call edge, 386 matched by ALLOC_RE, none unlisted). * chore(changelog): add fragment for #8207 --------- Co-authored-by: Ralph Küpper <ralph@skelpo.com>
1 parent f2554be commit 07c8040

2 files changed

Lines changed: 31 additions & 1 deletion

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
The GC root-dominance symbol scan accepts the `C-unwind` ABI, restoring the
2+
`gc-root-dominance` and `gc-root-dominance-statepoints` gates. Both were red on
3+
main with `POLL_CAPABLE_RUNTIME entries that name no runtime symbol:
4+
js_closure_call2`.
5+
6+
The entry was correct. `js_closure_call2` is a real, exported runtime symbol;
7+
what the scan could not see was its ABI string, because `runtime_symbols`
8+
matched `extern "C" fn js_*` and the function is declared `extern "C-unwind"`.
9+
The audit's own message warns against the wrong repair ("do not just delete it,
10+
or the audit goes green and the hole stays"), which is exactly the trap: the
11+
name was never wrong.
12+
13+
`"C-unwind"` is a distinct ABI string but the same exported C symbol, and the
14+
runtime uses it for every entry point a JS exception may unwind through — today
15+
18 symbols, including `js_throw`, the whole `js_native_call_method*` dispatch
16+
family, the `js_typed_feedback_native_call_*` paths, and `js_closure_call2`.
17+
Those are the allocating, poll-capable calls the analysis exists to reason
18+
about, so every consumer of `runtime_symbols` had been under-counting them; the
19+
phantom entry was the symptom that made a wider blindness visible.
20+
21+
The scan now accepts both spellings, taking `runtime_symbols` from 3803 to 3821
22+
exported symbols.

scripts/gc_root_dominance_check.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,15 @@ def build_cfg(f):
660660
# Deleting a dead alternative changes NOTHING about what the checker matches --
661661
# that is what "matches no symbol" means -- so this is not a narrowing.
662662

663-
_EXTERN_C_FN_RE = re.compile(r'extern\s+"C"\s+fn\s+(js_\w+)')
663+
# `"C-unwind"` counts too. It is a distinct ABI string but the SAME exported
664+
# C symbol, and the runtime uses it for every entry point a JS exception may
665+
# unwind through -- `js_throw`, the whole `js_native_call_method*` dispatch
666+
# family, `js_closure_call2`. Matching only `"C"` made those 18 symbols
667+
# invisible to every consumer of `runtime_symbols`: `--audit-poll-capable`
668+
# reported `js_closure_call2` as naming nothing (it names a real, exported
669+
# symbol), and the alloc/poll classifications silently under-counted the
670+
# calls most likely to allocate. The name was never wrong; the scanner was.
671+
_EXTERN_C_FN_RE = re.compile(r'extern\s+"C(?:-unwind)?"\s+fn\s+(js_\w+)')
664672

665673
# The runtime crates that export the C-ABI surface perry-codegen calls.
666674
SYMBOL_ROOTS = ("crates/perry-runtime/src", "crates/perry-stdlib/src")

0 commit comments

Comments
 (0)