perf(codegen): streamline async control cells - #8012
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughCompiler-private async and generator control cells now use direct typed LLVM payload loads and stores for preallocated ChangesAsync control-cell access
Estimated code review effort: 2 (Simple) | ~10 minutes Mergeability Score: 🟡 Moderate · up to This change uses direct access for compiler-private async control cells; if an access occurs before the cell is preallocated, generated code could crash. That unresolved correctness risk should be fixed or explicitly accepted before merging. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
crates/perry-codegen/tests/native_proof_regressions.rs (1)
7098-7122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExpand the regression to cover the full eligibility boundary.
The fixture declares
__gen_state,__gen_done, and__gen_executing, but not__gen_pending_type. It also contains no ordinary user capture. These assertions can pass while__gen_pending_typestill uses checked access or while an ordinary capture is incorrectly lowered through the direct path.Add a pending-type read/write and a paired ordinary-capture case, or verify that existing tests cover both paths.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/perry-codegen/tests/native_proof_regressions.rs` around lines 7098 - 7122, Expand the regression fixture and assertions around the existing control-lowering checks to cover __gen_pending_type reads/writes and a paired ordinary user capture. Verify that pending-type access uses the intended direct typed path, while the ordinary capture retains checked box access, ensuring both sides of the eligibility boundary are exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/perry-codegen/src/expr/mod.rs`:
- Around line 2300-2333: Replace the name-based checks in
is_compiler_private_async_i32_control_local and related classifiers with
compiler-generated identity/IDs, or reserve the __gen_state, __gen_pending_type,
__gen_done, and __gen_executing names so user locals cannot match them. Ensure
ordinary captured user bindings continue through boxed-variable lowering without
primitive-cell truncation, and add a regression covering a captured user binding
with one of these names.
---
Nitpick comments:
In `@crates/perry-codegen/tests/native_proof_regressions.rs`:
- Around line 7098-7122: Expand the regression fixture and assertions around the
existing control-lowering checks to cover __gen_pending_type reads/writes and a
paired ordinary user capture. Verify that pending-type access uses the intended
direct typed path, while the ordinary capture retains checked box access,
ensuring both sides of the eligibility boundary are exercised.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: a5e8a3d3-6ef9-44bf-8f20-bfe25ac5cdbf
📒 Files selected for processing (4)
changelog.d/8012-async-control-cells.mdcrates/perry-codegen/src/expr/mod.rscrates/perry-codegen/src/stmt/let_stmt.rscrates/perry-codegen/tests/native_proof_regressions.rs
| /// Load a compiler-private async i32 control cell directly. | ||
| /// | ||
| /// These cells are allocated by `Stmt::PreallocateBoxes` before the generated | ||
| /// state-machine closures are created. Unlike a general user capture, the | ||
| /// pointer is therefore compiler-minted and its pointee representation is | ||
| /// proven: the `I32Box` value is the first (and only) field. Keep ordinary | ||
| /// boxes on the checked runtime path; this helper is deliberately reachable | ||
| /// only from the `is_compiler_private_async_i32_control_local` arms below. | ||
| pub(crate) fn load_async_i32_control_cell(ctx: &mut FnCtx<'_>, cell: &str) -> String { | ||
| let ptr = ctx.block().inttoptr(I64, cell); | ||
| ctx.block().load(I32, &ptr) | ||
| } | ||
|
|
||
| /// Store a compiler-private async i32 control cell directly. See | ||
| /// `load_async_i32_control_cell` for the allocation/provenance proof. | ||
| pub(crate) fn store_async_i32_control_cell(ctx: &mut FnCtx<'_>, cell: &str, value: &str) { | ||
| let ptr = ctx.block().inttoptr(I64, cell); | ||
| ctx.block().store(I32, value, &ptr); | ||
| } | ||
|
|
||
| /// Load a compiler-private async boolean control cell directly. `BoolBox`'s | ||
| /// value is a Rust `bool`, represented as LLVM i1 at the FFI boundary. | ||
| pub(crate) fn load_async_i1_control_cell(ctx: &mut FnCtx<'_>, cell: &str) -> String { | ||
| let ptr = ctx.block().inttoptr(I64, cell); | ||
| ctx.block().load(I1, &ptr) | ||
| } | ||
|
|
||
| /// Store a compiler-private async boolean control cell directly. See | ||
| /// `load_async_i1_control_cell` for the representation proof. | ||
| pub(crate) fn store_async_i1_control_cell(ctx: &mut FnCtx<'_>, cell: &str, value: &str) { | ||
| let ptr = ctx.block().inttoptr(I64, cell); | ||
| ctx.block().store(I1, value, &ptr); | ||
| } | ||
|
|
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
ast-grep outline crates/perry-codegen/src/expr/mod.rs \
--match 'load_async_i32_control_cell' --view expanded
ast-grep outline crates/perry-codegen/src/stmt/mod.rs \
--match 'emit_preallocate_boxes' --view expanded
rg -n -C 14 \
'PreallocateBoxes|collect_prealloc_box_ids_in_stmts|compiler_private_async_i32_control_locals|compiler_private_async_i1_control_locals|is_compiler_private_async_i32_control_local|is_compiler_private_async_i1_control_local|load_boxed_local_pointer|load_async_i32_control_cell|store_async_i32_control_cell|load_async_i1_control_cell|store_async_i1_control_cell' \
crates/perry-codegen crates/perry-transformRepository: PerryTS/perry
Length of output: 50372
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- expr helpers and predicates ---'
sed -n '2240,2350p' crates/perry-codegen/src/expr/mod.rs
printf '%s\n' '--- statement lowering and preallocation ---'
sed -n '540,690p' crates/perry-codegen/src/stmt/mod.rs
printf '%s\n' '--- direct helper call sites ---'
rg -n -C 8 \
'load_async_i32_control_cell|store_async_i32_control_cell|load_async_i1_control_cell|store_async_i1_control_cell|is_compiler_private_async_i32_control_local|is_compiler_private_async_i1_control_local' \
crates/perry-codegen/src
printf '%s\n' '--- all PreallocateBoxes construction sites ---'
rg -n -C 10 \
'PreallocateBoxes|PreallocateTdzBoxes|emit_preallocate_boxes|collect_prealloc_box_ids_in_stmts' \
crates/perry-transform/src crates/perry-codegen/src \
-g '*.rs' \
| head -n 1400Repository: PerryTS/perry
Length of output: 50370
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
roots = [Path("crates/perry-transform/src"), Path("crates/perry-codegen/src")]
files = [p for root in roots for p in root.rglob("*.rs")]
for p in files:
text = p.read_text()
for match in re.finditer(r"PreallocateBoxes|PreallocateTdzBoxes", text):
line = text.count("\n", 0, match.start()) + 1
start = max(0, line - 4)
lines = text.splitlines()
end = min(len(lines), line + 8)
print(f"\n--- {p}:{line} ---")
for n in range(start + 1, end + 1):
print(f"{n}: {lines[n-1]}")
PYRepository: PerryTS/perry
Length of output: 50371
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- complete preallocation lowering ---'
sed -n '680,755p' crates/perry-codegen/src/stmt/mod.rs
printf '%s\n' '--- generator body construction ---'
sed -n '620,745p' crates/perry-transform/src/generator/lower.rs
printf '%s\n' '--- async transform control-local setup ---'
rg -n -C 18 \
'compiler_private_async_i32_control_locals|compiler_private_async_i1_control_locals|async_i32|async_i1|control_local|control cell|PreallocateBoxes' \
crates/perry-transform/src/async_to_generator.rs \
crates/perry-transform/src/generator \
crates/perry-codegen/src/codegen \
crates/perry-codegen/src/expr \
-g '*.rs' \
| head -n 1800
printf '%s\n' '--- all private-set references ---'
rg -n -C 6 \
'compiler_private_async_i32_control_locals|compiler_private_async_i1_control_locals' \
crates -g '*.rs'Repository: PerryTS/perry
Length of output: 50372
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
needles = (
"compiler_private_async_i32_control_locals",
"compiler_private_async_i1_control_locals",
)
for p in Path("crates").rglob("*.rs"):
lines = p.read_text().splitlines()
for i, line in enumerate(lines):
if any(n in line for n in needles):
lo = max(0, i - 20)
hi = min(len(lines), i + 21)
print(f"\n--- {p}:{i+1} ---")
for n in range(lo, hi):
print(f"{n+1}: {lines[n]}")
PYRepository: PerryTS/perry
Length of output: 50370
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- private-set mutations and definitions ---'
rg -n \
'compiler_private_async_i32_control_locals|compiler_private_async_i1_control_locals' \
crates/perry-codegen crates/perry-transform \
-g '*.rs' \
| grep -E 'insert|extend|union|HashSet|pub |fn |struct |=' \
| head -n 500
printf '%s\n' '--- all async control-local identifiers and types ---'
rg -n -C 10 \
'__gen_state|__gen_done|__gen_executing|__gen_pending_type|__gen_pending_value|pending_type_id|state_id|done_id|executing_id' \
crates/perry-transform/src/generator \
-g '*.rs' \
| head -n 1800
printf '%s\n' '--- function-body lowering order ---'
rg -n -C 18 \
'lower_stmts|lower_stmt|func.body|collect_prealloc_box_ids_in_stmts|prealloc_boxes' \
crates/perry-codegen/src/codegen crates/perry-codegen/src/stmt crates/perry-codegen/src/expr \
-g '*.rs' \
| head -n 2200Repository: PerryTS/perry
Length of output: 50370
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
for p in Path("crates").rglob("*.rs"):
text = p.read_text()
for name in (
"compiler_private_async_i32_control_locals",
"compiler_private_async_i1_control_locals",
):
for m in re.finditer(re.escape(name), text):
line = text.count("\n", 0, m.start()) + 1
before = text[max(0, m.start()-180):m.start()]
after = text[m.end():m.end()+220]
if any(op in before[-80:] + after[:120] for op in ("insert", "extend", "union", "collect", "HashSet", "=")):
print(f"{p}:{line}: {text.splitlines()[line-1].strip()}")
PYRepository: PerryTS/perry
Length of output: 2472
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- private-ID scanner population ---'
sed -n '1540,1650p' crates/perry-codegen/src/codegen/mod.rs
sed -n '1700,1790p' crates/perry-codegen/src/codegen/mod.rs
printf '%s\n' '--- scanner definitions and call sites ---'
rg -n -C 24 \
'collect_compiler_private|compiler_private_async|__gen_state|__gen_done|__gen_executing|__gen_pending' \
crates/perry-codegen/src/codegen/mod.rs \
crates/perry-codegen/src \
-g '*.rs' \
| head -n 1800
printf '%s\n' '--- function lowering entry and body traversal ---'
rg -n -C 22 \
'lower_stmts|lower_stmt|f.body|method.body|body.iter|lower_function|lower_method' \
crates/perry-codegen/src/codegen/function.rs \
crates/perry-codegen/src/codegen/method.rs \
crates/perry-codegen/src/codegen/closure.rs \
crates/perry-codegen/src/stmt/mod.rs \
-g '*.rs' \
| head -n 2200Repository: PerryTS/perry
Length of output: 50370
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
p = Path("crates/perry-codegen/src/codegen/mod.rs")
lines = p.read_text().splitlines()
for i, line in enumerate(lines):
if "compiler_private_async" in line or "collect_compiler_private" in line:
lo, hi = max(0, i-35), min(len(lines), i+45)
print(f"\n--- {p}:{i+1} ---")
for n in range(lo, hi):
print(f"{n+1}: {lines[n]}")
PYRepository: PerryTS/perry
Length of output: 50370
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- compiler-private collector definition ---'
rg -n 'fn collect_compiler_private_async_control_locals_in_stmts|collect_compiler_private_async_control_locals_in_stmts' \
crates/perry-codegen/src/boxed_vars.rs
sed -n '1,180p' crates/perry-codegen/src/boxed_vars.rs
printf '%s\n' '--- closure lowering and capture slot setup ---'
rg -n -C 20 \
'closure_captures|load_boxed_local_pointer|js_closure_get_capture_bits|collect_prealloc_box_ids_in_stmts|PreallocateBoxes' \
crates/perry-codegen/src/codegen/closure.rs \
crates/perry-codegen/src/boxed_vars.rs \
crates/perry-codegen/src/expr/mod.rs \
-g '*.rs' \
| head -n 1800
printf '%s\n' '--- codegen body lowering calls ---'
rg -n -C 16 \
'lower_stmts|lower_stmt|lower_expr|f.body|body.as_slice|body.iter' \
crates/perry-codegen/src/codegen/function.rs \
crates/perry-codegen/src/codegen/method.rs \
crates/perry-codegen/src/codegen/closure.rs \
-g '*.rs' \
| head -n 1800Repository: PerryTS/perry
Length of output: 50370
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
p = Path("crates/perry-codegen/src/boxed_vars.rs")
lines = p.read_text().splitlines()
for i, line in enumerate(lines):
if "collect_compiler_private_async_control_locals_in_stmts" in line:
lo, hi = max(0, i-8), min(len(lines), i+120)
print(f"--- {p}:{i+1} ---")
for n in range(lo, hi):
print(f"{n+1}: {lines[n]}")
PYRepository: PerryTS/perry
Length of output: 50369
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- all PreallocateBoxes constructors ---'
rg -n -C 5 \
'Stmt::PreallocateBoxes\(|Stmt::PreallocateTdzBoxes\(' \
crates -g '*.rs'
printf '%s\n' '--- internal control names outside generator lowering ---'
rg -n -C 5 \
'__gen_state|__gen_pending_type|__gen_done|__gen_executing' \
crates tests test-files 2>/dev/null \
| head -n 1800
printf '%s\n' '--- tests for private control cells and preallocation ---'
rg -n -C 8 \
'compiler_private_async|primitive_i32_control_cell|primitive_i1_control_cell|PreallocateBoxes|__gen_state|__gen_done|__gen_executing|__gen_pending_type' \
crates tests test-files 2>/dev/null \
| head -n 2200Repository: PerryTS/perry
Length of output: 50370
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
# Read-only structural verifier for the invariant relevant to this review.
files = [p for root in (Path("crates/perry-transform/src"), Path("crates/perry-codegen/src"))
for p in root.rglob("*.rs")]
constructors = []
for p in files:
for i, line in enumerate(p.read_text().splitlines(), 1):
if re.search(r'Stmt::Preallocate(?:Tdz)?Boxes\s*\(', line):
constructors.append((str(p), i, line.strip()))
print("PREALLOC_CONSTRUCTORS")
for item in constructors:
print(f"{item[0]}:{item[1]}:{item[2]}")
lower = Path("crates/perry-transform/src/generator/lower.rs").read_text()
print("\nGENERATOR_FIRST_PREALLOC",
bool(re.search(r'new_body\.push\(Stmt::PreallocateBoxes\(prealloc_ids\)\);', lower)))
collector = Path("crates/perry-codegen/src/boxed_vars.rs").read_text()
rules = {
"i32_names": bool(re.search(r'"__gen_state"\s*\|\s*"__gen_pending_type"', collector)),
"i1_names": bool(re.search(r'"__gen_done"\s*\|\s*"__gen_executing"', collector)),
"requires_preallocated": bool(re.search(r'if preallocated\.contains\(id\)', collector)),
}
print("COLLECTOR_RULES", rules)
for name in ("__gen_state", "__gen_pending_type", "__gen_done", "__gen_executing"):
occurrences = []
for p in files:
for i, line in enumerate(p.read_text().splitlines(), 1):
if name in line:
occurrences.append(f"{p}:{i}")
print(name, len(occurrences), *occurrences[:12], sep=" | ")
PYRepository: PerryTS/perry
Length of output: 12687
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- all-repository preallocation construction ---'
rg -n -C 8 \
'PreallocateBoxes|PreallocateTdzBoxes|prealloc.*box|preallocate.*box' \
. -g '*.rs' \
| grep -E 'push|insert|emit|lower|Preallocate' \
| head -n 1800
printf '%s\n' '--- control-value coercion helpers ---'
rg -n -C 18 \
'lower_i32_control_store_value|lower_i1_control_store_value|compiler_private_async_control' \
crates/perry-codegen/src/expr/mod.rs \
crates/perry-codegen/src/stmt/let_stmt.rs \
-g '*.rs' \
| head -n 1600
printf '%s\n' '--- generated-name reservation or user-name filtering ---'
rg -n -C 8 \
'__gen_state|__gen_pending_type|__gen_done|__gen_executing|reserved.*name|internal.*name|name.*reserved' \
crates/perry-hir crates/perry-transform crates/perry-codegen \
-g '*.rs' \
| head -n 1800Repository: PerryTS/perry
Length of output: 50370
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
p = Path("crates/perry-codegen/src/expr/mod.rs")
lines = p.read_text().splitlines()
for i, line in enumerate(lines):
if "lower_i32_control_store_value" in line or "lower_i1_control_store_value" in line:
lo, hi = max(0, i-8), min(len(lines), i+65)
print(f"\n--- {p}:{i+1} ---")
for n in range(lo, hi):
print(f"{n+1}: {lines[n]}")
PYRepository: PerryTS/perry
Length of output: 13622
Avoid name-based classification of user locals
A preallocated user binding named __gen_state, __gen_pending_type, __gen_done, or __gen_executing matches the compiler-private classifier in crates/perry-codegen/src/boxed_vars.rs:1427-1438. Its writes then use primitive-cell lowering, so a Number binding can be truncated to i32. Use compiler-generated IDs or reserve these names. Add a regression with a captured user binding.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/perry-codegen/src/expr/mod.rs` around lines 2300 - 2333, Replace the
name-based checks in is_compiler_private_async_i32_control_local and related
classifiers with compiler-generated identity/IDs, or reserve the __gen_state,
__gen_pending_type, __gen_done, and __gen_executing names so user locals cannot
match them. Ensure ordinary captured user bindings continue through
boxed-variable lowering without primitive-cell truncation, and add a regression
covering a captured user binding with one of these names.
Source: Coding guidelines
d5cdf3f to
5bec742
Compare
Closes #8008.
Summary
Mechanism and sized lever
The async transform preallocates these four control cells and proves both their identity and primitive type. Their
I32Box/BoolBoxallocations are stable, single-field#[repr(C, align(8))]objects, but every state-machine get/set still called a runtime helper that revalidated the pointer against a thread-local registry.On the refreshed pure-async profile,
is_registered_i32_box_ptrandis_registered_bool_box_ptraccount for 7.13% of leaf samples. Direct typed access to only the compiler-minted controls removes that lookup; allocation and registry insertion remain unchanged, and unproven/user boxes retain the #4898 checked path. The measured pure-async instruction reduction is 7.08%.Current-main decomposition and A/B
Current
mainisf58b73f(0.5.1508). Both arms use the exact samef58b73fruntime/stdlib archives with auto-optimize disabled, isolating compiler codegen from runtime and feature-set drift. Retired instructions and cycles are best of 13 interleaved runs; wall is median of the same 13. The host load was high, so retired instructions are the primary result and wall is reported as required directional evidence.a2_full1xa2_sync1xa2_pure1xa2_pureobj1xa2_pall1xThe original 85.2% async share is now 79.5% on current main (
1 - 247.1 / 1207.2) after #7927, #7924, #7939, and subsequent mainline work. This patch reduces the full probe another 2.10%, while the synchronous and Promise.all-only instruction controls are flat. The selective pure-async result sizes the named control-cell validation mechanism rather than attributing the remaining gap to undifferentiated “async overhead.”Validation
cargo test --release -p perry-codegen(939 unit tests, 263 native proof regressions, integrations and doctests)cargo clippy -p perry-codegen --lib --tests./scripts/pre-tag-check.sh --quickfastify/jsonwebtokenwere absent (the other 40 passed)No version bump is included.
Summary by CodeRabbit
Performance
Bug Fixes