Repository navigation
jit: compile loops whose local is unbound on some predecessor (DELETE_FAST panic + LOAD_FAST_CHECK abort) - #696
Conversation
|
Warning Review limit reached
Next review available in: 41 seconds Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (7)
✨ Finishing Touches🧪 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.
💡 Codex Review
https://github.com/youknowone/pyre/blob/0b392060c38829ec22a0ec2b1fffc30c78d3647c/pyre-object/src/setobject.rs#L290
Register callback-free helpers as fnaddrs
When these set paths are traced by the JIT, begin_callback_free_probe/end_callback_free_probe and the other new #[dont_look_inside] TLS helpers have to be residualized through jit_trace_fnaddrs; I checked pyre-interpreter/src/jit_fnaddr.rs:1088-1168, and it still registers the existing dict_eq_hook helpers but not these new callback-free ones. In this repo, unregistered helper paths are represented as symbolic fnaddr placeholders, so compiled traces that execute set contains/add/discard can end up with an unpatchable residual call instead of the real TLS guard.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
🤖 Codex parity reviewStatic analysis of this diff vs the local RPython/PyPy sources (commit 6e86efc). Files in the reviewed diff1. Regressions to PyPy parity introduced by this patchNone. 2. Other mismatches introduced by this patch
3. Pre-existing mismatches (already present before this patch)
4. Structural adaptations
|
Assisted-by: Claude
Assisted-by: Claude
Use callback-free probes for builtin keys and restartable entry scans when equality can call user code. Route infallible set operations through the same guarded helpers and add a reentrant mutation synth case. Assisted-by: Claude
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1f540b31b8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if equal { | ||
| return Ok((Some(i), key)); |
There was a problem hiding this comment.
Restart when equality mutates the matched set entry
When a same-hash key's __eq__ mutates this set and then returns True, this branch returns the pre-callback index without first verifying that the candidate entry is still present at that index. That makes the fallback path disagree with the RPython lookup it is porting, which rechecks the table/key before honoring found; otherwise contains can report a removed key as present, add can skip inserting the new key, and discard can remove whatever shifted into the stale index.
AGENTS.md reference: AGENTS.md:L194-L195
Useful? React with 👍 / 👎.
`capture_snapshot_for_last_guard`'s multi-frame `Snapshot` initializer was the one construction site left without the field, so the crate did not compile. Assisted-by: Claude
`begin_callback_free_probe`, `end_callback_free_probe`, `callback_free_probe_active`, `callback_free_probe_broken` and `break_callback_free_probe` had no `jit_trace_fnaddrs` entry, so a residual call to one resolved to a `symbolic_fnaddr` placeholder instead of the helper's address. Assisted-by: Claude
Rebased onto
51cf07eb732. The earliersetprobe commit was dropped: #664 replaced the callback-visible table copy inw_set_{insert,contains,discard}_key_checkedwith a plain probe, so the O(n)-per-operation cost it removed no longer exists (synth/dict_setis 0.72s on this base, membership flat across set sizes).What remains are two defects with one root cause:
codewriter.rshad no representation for a local that is unbound on at least one predecessor block (the framestate union yields no SSA value for the slot).1.
DELETE_FAST— JIT-only compile panic (process dies)Dropping the SSA value made
DELETE_FASTemit an exitless permanent-abort block; flattening then misclassified its three incoming live values as return arguments and hitmake_return's arity check. The lowering now reads the physical frame slot and keeps the bound/unbound branch.Discriminators:
del xunconditional in the loop is fine, andx = Nonein place ofdel xis fine — only the branch-armdelpanicked.PYRE_NO_JIT=1was always correct.2.
LOAD_FAST_CHECK— conditional assignment never compiledif …: x = …is the ordinary Python shape, and the arm calledemit_abort_permanent!whenever the slot had no SSA value, so every trace hit the abort marker and the loop was never compiled.The arm's comment records the hazard it was avoiding: the residual +
GuardNoException+ push leaves a normal-returnFinishcontinuation, and a later guard-failure bridge resolves into that return, silently swallowing the raise. The fix avoids raising from compiled code at all, reusing the CFG shape the FOR_ITER exhaustion lowering already uses in this file: read the slot from the vable,ptr_nonzero, continue on the bound arm, and side-exit at this opcode on null so the interpreter re-runsLOAD_FAST_CHECKand raisesUnboundLocalErroritself.synth/load_fast_checkloops_compiled/loops_abortedSame numbers on both backends, and level with the control that hoists
x = 0before the loop soLOAD_FASTis emitted instead (0.04s). Output unchanged at979999300000.Verification
Rebuilt both backends at HEAD, then:
UnboundLocalErroron dynasm and cranelift, matching CPython;check.py: dynasm 241/241, cranelift 241/241.🤖 Generated with Claude Code