-
-
Notifications
You must be signed in to change notification settings - Fork 159
perf(codegen): fix a root-reload allowlist symbol that has never matched anything #8106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| **Fixed a symbol name in codegen's root-reload allowlist that has never matched | ||
| anything (#5094).** `root_reload.rs`'s `NON_COLLECTING` listed | ||
| `js_gc_layout_note_slot`; the runtime exports `js_gc_note_slot_layout` | ||
| (`gc/layout.rs:814`) and `js_gc_note_slot_layout_aware` (`:833`). No symbol by | ||
| the old spelling exists anywhere in the tree — `gc_call_effects.rs` and all | ||
| twelve tests that reference these helpers use the real names. | ||
|
|
||
| The failure mode is why it survived. The file's own contract says a helper | ||
| missing from the list "is treated as collecting, which inserts a reload the | ||
| checker would not have demanded — a load, not a bug". So the typo was | ||
| safe-direction and silent: every emitted slot-layout note forced a root reload | ||
| instead of none, including the one call per guarded array element store | ||
| (`expr/index_set_guarded.rs`), which is the hot path #5094 exists for. | ||
|
|
||
| Both real names are now listed, and the phantom is removed from | ||
| `scripts/gc_root_dominance_check.py` as well — that file carried it too, | ||
| harmlessly, because it also carries the correct spelling. `_aware` is added | ||
| there alongside: it is `js_gc_note_slot_layout` behind an early return taken | ||
| when neither the new nor the old bits are pointer-bearing, so it does strictly | ||
| less than the entry point the set already admits — the same "differ only by | ||
| doing less" argument the file already accepts for `declare` vs `init`. The | ||
| one-way containment invariant (`root_reload.rs`'s list is a subset of the | ||
| checker's) is preserved in both directions of the edit. | ||
|
|
||
| Two regression tests in `root_reload_tests.rs`, both failing on the parent | ||
| commit: every `NON_COLLECTING` entry must be a name | ||
| `gc_call_effects::classify_direct_callee` answers `CannotCollect` for — which is | ||
| the "the two lists must agree" rule the checker's own comment states and nothing | ||
| enforced — and the two note helpers are pinned by name, because the bug was a | ||
| *missing* entry and a containment test alone is satisfied by an empty set. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -468,15 +468,12 @@ def build_cfg(f): | |
| # cannot -- side-table metadata writes through the system allocator, which | ||
| # arms no Perry GC trigger. | ||
| "js_gc_init_typed_shape_layout", "js_gc_declare_typed_shape_layout", | ||
| "js_gc_layout_note_slot", | ||
| "js_write_barrier_root_nanbox", "js_write_barrier_slot", | ||
| "js_runtime_write_barrier_slot", "js_gc_register_global_root", | ||
| "js_gc_register_global_root", | ||
| # pure value predicates / bit twiddling | ||
| "js_is_truthy", "js_nanbox_get_pointer", "js_value_is_object", | ||
| "js_value_is_string", "js_typeof_tag", | ||
| "js_is_truthy", "js_nanbox_get_pointer", | ||
|
Comment on lines
470
to
+474
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
rg -n -C 4 \
'js_gc_forget_object_layout|js_array_declare_all_pointer_elements|NON_COLLECTING|NONCOLLECTING' \
crates/perry-codegen/src/root_reload.rs \
scripts/gc_root_dominance_check.pyRepository: PerryTS/perry Length of output: 13176 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- root_reload.rs ---'
sed -n '150,245p' crates/perry-codegen/src/root_reload.rs
printf '%s\n' '--- gc_root_dominance_check.py ---'
sed -n '440,525p' scripts/gc_root_dominance_check.py
printf '%s\n' '--- alternate references ---'
rg -n -C 3 \
'js_gc_forget_object_layout|js_array_declare_all_pointer_elements|root_reload|NONCOLLECTING' \
scripts/gc_root_dominance_check.py crates/perry-codegen/src/root_reload.rsRepository: PerryTS/perry Length of output: 19877 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- helper definitions and declarations ---'
rg -n -C 5 \
'js_gc_forget_object_layout|js_array_declare_all_pointer_elements|every_non_collecting_entry_is_a_real_runtime_export|subset|NON_COLLECTING' \
crates scripts tests 2>/dev/null || true
printf '%s\n' '--- exact set membership ---'
python3 - <<'PY'
from pathlib import Path
import re
rust = Path("crates/perry-codegen/src/root_reload.rs").read_text()
checker = Path("scripts/gc_root_dominance_check.py").read_text()
rust_block = re.search(r'const NON_COLLECTING:.*?;\n', rust, re.S).group(0)
py_block = re.search(r'^NONCOLLECTING = \{.*?^\}', checker, re.S | re.M).group(0)
rust_names = set(re.findall(r'"([^"]+)"', rust_block))
py_names = set(re.findall(r'"([^"]+)"', py_block))
print("Rust-only:", sorted(rust_names - py_names))
print("Python-only:", sorted(py_names - rust_names))
for name in ("js_gc_forget_object_layout", "js_array_declare_all_pointer_elements",
"js_gc_note_slot_layout", "js_gc_note_slot_layout_aware",
"js_string_addref_if_heap_string"):
print(f"{name}: rust={name in rust_names}, python={name in py_names}")
PYRepository: PerryTS/perry Length of output: 50370 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- exact helper definitions ---'
rg -n -C 10 \
'pub extern "C" fn js_gc_forget_object_layout|pub extern "C" fn js_array_declare_all_pointer_elements' \
crates/perry-runtime
printf '%s\n' '--- exact list membership and subset result ---'
python3 - <<'PY'
from pathlib import Path
import re
rust = Path("crates/perry-codegen/src/root_reload.rs").read_text()
checker = Path("scripts/gc_root_dominance_check.py").read_text()
rust_match = re.search(r'const NON_COLLECTING:.*?^\\];', rust, re.S | re.M)
py_match = re.search(r'^NONCOLLECTING = \\{.*?^\\}', checker, re.S | re.M)
if not rust_match or not py_match:
raise SystemExit("could not isolate one or both collections")
rust_names = set(re.findall(r'"([^"]+)"', rust_match.group(0)))
py_names = set(re.findall(r'"([^"]+)"', py_match.group(0)))
print("rust_count", len(rust_names))
print("python_count", len(py_names))
print("rust_only", sorted(rust_names - py_names))
print("python_only", sorted(py_names - rust_names))
for name in (
"js_gc_forget_object_layout",
"js_array_declare_all_pointer_elements",
"js_gc_note_slot_layout",
"js_gc_note_slot_layout_aware",
"js_string_addref_if_heap_string",
):
print(name, "rust=" + str(name in rust_names), "python=" + str(name in py_names))
PY
printf '%s\n' '--- consistency-test references only ---'
rg -n \
'every_non_collecting_entry_is_a_real_runtime_export|root_reload.rs|NONCOLLECTING|NON_COLLECTING' \
crates/perry-codegen scripts/gc_root_dominance_check.pyRepository: PerryTS/perry Length of output: 4025 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- compiler call-effect classifications ---'
rg -n -C 5 \
'js_gc_forget_object_layout|js_array_declare_all_pointer_elements' \
crates/perry-codegen/src/gc_call_effects.rs crates/perry-codegen
printf '%s\n' '--- helper bodies ---'
sed -n '455,490p' crates/perry-runtime/src/gc/layout_tables.rs
sed -n '1498,1525p' crates/perry-runtime/src/array/header.rs
rg -n -C 8 \
'fn layout_forget_object|fn layout_init_all_pointer_slots|layout_forget_object|layout_init_all_pointer_slots' \
crates/perry-runtime/src/gc crates/perry-runtime/src/array
printf '%s\n' '--- exact list membership and subset result ---'
python3 - <<'PY'
from pathlib import Path
import re
rust = Path("crates/perry-codegen/src/root_reload.rs").read_text()
checker = Path("scripts/gc_root_dominance_check.py").read_text()
rust_match = re.search(r'const NON_COLLECTING:.*?^\];', rust, re.S | re.M)
py_match = re.search(r'^NONCOLLECTING = \{.*?^\}', checker, re.S | re.M)
if not rust_match or not py_match:
raise SystemExit(
f"could not isolate collections: rust={bool(rust_match)} python={bool(py_match)}"
)
rust_names = set(re.findall(r'"([^"]+)"', rust_match.group(0)))
py_names = set(re.findall(r'"([^"]+)"', py_match.group(0)))
print("rust_count", len(rust_names))
print("python_count", len(py_names))
print("rust_only", sorted(rust_names - py_names))
print("python_only", sorted(py_names - rust_names))
for name in (
"js_gc_forget_object_layout",
"js_array_declare_all_pointer_elements",
"js_gc_note_slot_layout",
"js_gc_note_slot_layout_aware",
"js_string_addref_if_heap_string",
):
print(name, "rust=" + str(name in rust_names), "python=" + str(name in py_names))
PY
printf '%s\n' '--- consistency-test references ---'
rg -n \
'every_non_collecting_entry_is_a_real_runtime_export|root_reload.rs|NONCOLLECTING|NON_COLLECTING' \
crates/perry-codegen scripts/gc_root_dominance_check.pyRepository: PerryTS/perry Length of output: 50369 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
rust = Path("crates/perry-codegen/src/root_reload.rs").read_text()
checker = Path("scripts/gc_root_dominance_check.py").read_text()
rust_match = re.search(r"const NON_COLLECTING:.*?^\];", rust, re.S | re.M)
py_match = re.search(r"^NONCOLLECTING = \{.*?^\}", checker, re.S | re.M)
assert rust_match and py_match, "failed to isolate allowlists"
rust_names = set(re.findall(r'"([^"]+)"', rust_match.group(0)))
py_names = set(re.findall(r'"([^"]+)"', py_match.group(0)))
for name in (
"js_gc_forget_object_layout",
"js_array_declare_all_pointer_elements",
"js_gc_note_slot_layout",
"js_gc_note_slot_layout_aware",
"js_string_addref_if_heap_string",
):
print(f"{name}: root_reload={name in rust_names}, checker={name in py_names}")
print("root_reload_not_subset_of_checker:",
sorted(rust_names - py_names))
PYRepository: PerryTS/perry Length of output: 570 Add both helpers to
🤖 Prompt for AI Agents |
||
| # inline-cache guards: pure reads | ||
| "js_typed_feedback_closure_direct_call_guard", | ||
| "js_typed_feedback_shape_guard", "js_typed_feedback_note", | ||
| # ctor identity selection | ||
| "js_ctor_return_override", | ||
| "llvm.lifetime.start.p0", "llvm.lifetime.end.p0", | ||
|
|
@@ -505,7 +502,15 @@ def build_cfg(f): | |
| "js_closure_unbox_callee_checked", | ||
| # object/this_binding.rs:160 -- a thread-local cell swap | ||
| "js_implicit_this_set", "js_implicit_this_get", | ||
| "js_gc_note_slot_layout", "js_string_addref_if_heap_string", | ||
| # `js_gc_note_slot_layout` (gc/layout.rs:814) and its `_aware` sibling | ||
| # (:833). `_aware` is the same body behind an early return taken when | ||
| # neither the new nor the old bits are pointer-bearing, so it does strictly | ||
| # LESS than the entry point beside it -- the same "differ only by doing | ||
| # less" argument this file already accepts for `declare` vs `init` above. | ||
| # A phantom third spelling, `js_gc_layout_note_slot`, sat in this set (and | ||
| # in `root_reload.rs`) and matched no symbol in the tree. | ||
| "js_gc_note_slot_layout", "js_gc_note_slot_layout_aware", | ||
| "js_string_addref_if_heap_string", | ||
| # `js_get_string_pointer_unified` is deliberately NOT here. Its SSO branch | ||
| # calls `js_string_materialize_to_heap`, which allocates (value/nanbox.rs:268), | ||
| # so it is a collection point by this file's one-sided rule even though the | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the regression-test description.
Lines 25-29 say the test calls
gc_call_effects::classify_direct_callee.every_non_collecting_entry_is_a_real_runtime_exportonly searches source text forextern "C" fndefinitions. Either add the stated classifier assertion or describe the implemented export check.Based on learnings, changelog fragments must accurately describe shipped behavior.
🤖 Prompt for AI Agents
Source: Learnings