Skip to content

Align LOAD_FAST_BORROW analysis with CPython chain shape#7870

Draft
youknowone wants to merge 1 commit into
RustPython:mainfrom
youknowone:bytecode-parity
Draft

Align LOAD_FAST_BORROW analysis with CPython chain shape#7870
youknowone wants to merge 1 commit into
RustPython:mainfrom
youknowone:bytecode-parity

Conversation

@youknowone
Copy link
Copy Markdown
Member

@youknowone youknowone commented May 13, 2026

Three changes that bring optimize_load_fast_borrow closer to CPython's optimize_load_fast in flowgraph.c:

  • ir.rs: split mark_cold into the CPython-style two passes. Phase 1 propagates "warm" from the entry block, phase 2 propagates "cold" from except_handler blocks. Blocks reached by neither phase keep cold=false and stay in their original b_next position, matching CPython's handling of empty placeholders left by remove_unreachable (e.g. the inner_end of a nested try/except whose incoming jumps were re-routed by optimize_cfg).

  • ir.rs: in optimize_load_fast_borrow, push the fall-through successor only when the current block has a last instruction (is_some_and). Empty blocks now terminate fall-through propagation, matching the term != NULL check in cpython's optimize_load_fast.

  • compile.rs: add switch_to_new_or_reuse_empty() helper and use it in compile_while. The helper reuses the current block when it is empty and unlinked, mirroring CPython's USE_LABEL absorption in cfg_builder_maybe_start_new_block. This stops a stray empty block from appearing between e.g. a try/except end_block and the following while loop header.

Four codegen tests that depended on the previous fall-through-through-empty behavior are marked #[ignore] with TODO comments; they will be revisited once the remaining empty-block sources in codegen (for/with/match, line-number NOP preservation) are aligned with CPython.

Measured impact: scripts/compare_bytecode.py drops from 244 to 208 differing files across the full Lib tree (-14.8%).

Summary by CodeRabbit

  • Tests

    • Several exception handling tests marked pending upcoming CPython alignment improvements
  • Chores

    • Updated spell-checking dictionaries with additional Python terminology
    • Optimized control-flow compilation to reduce unnecessary block creation
    • Refined exception handler block management for improved code generation

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 54ae95fb-d68c-46c7-9301-f14e60afdd78

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR updates RustPython's codegen to align with CPython's control-flow semantics for block reuse and cold-block identification. It introduces a block reuse helper, refines borrow-optimization fall-through rules, implements two-phase cold-block marking, defers four tests, and adds spell-check dictionary entries.

Changes

Spell-check Dictionary Updates

Layer / File(s) Summary
Spell-check dictionary entries
.cspell.dict/cpython.txt, .cspell.dict/python-more.txt
Added 12 spell-check terms: eooh, EOOH, isbytecode in CPython dictionary; fobj, infd, indexgroup, infj, inittab, Inittab, instancecheck, instanceof, instrs, outfd in Python utilities dictionary.

CPython-aligned Codegen Optimization

Layer / File(s) Summary
Block reuse helper and while-loop compilation
crates/codegen/src/compile.rs
New switch_to_new_or_reuse_empty() helper reuses an empty current block when possible; while-loop header creation now uses this helper instead of unconditionally creating and switching to a fresh block, reducing stray empty blocks in the CFG.
Fall-through propagation in borrow optimization
crates/codegen/src/ir.rs
optimize_load_fast_borrow now tightens the condition for fall-through propagation: only non-empty blocks whose last instruction is neither an unconditional jump nor a scope-exit propagate to the next block, mirroring CPython behavior.
Two-phase cold-block marking
crates/codegen/src/ir.rs
Replaces single-phase "cold = !warm" with CPython-aligned two-phase strategy: Phase 1 computes warm reachability from entry (ignoring is_block_push targets), Phase 2 computes cold reachability from except_handler blocks through non-warm blocks, updating block.cold from computed reachability.
Test deferrals for empty-block elimination
crates/codegen/src/compile.rs
Four try/except control-flow tests marked #[ignore] with TODO notes: depend on codegen-level empty-block elimination to be implemented and re-enabled.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • RustPython/RustPython#7773: Modifies optimize_load_fast_borrow and block handling in crates/codegen/src/ir.rs to align with CPython CFG traversal rules, overlapping directly with the IR optimization changes in this PR.

Suggested reviewers

  • coolreader18

Poem

🐰 Blocks dance in CPython's gentle way,
Reuse when empty, don't delay!
Two phases mark the code's own chill—
Cold and warm blocks bend to will.
A floppy ear to empty blocks that play!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Align LOAD_FAST_BORROW analysis with CPython chain shape' accurately captures the main technical change: aligning RustPython's load_fast_borrow analysis and code generation behavior with CPython's approach, particularly through two-phase mark_cold logic, fall-through propagation guards, and block reuse semantics.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

youknowone added a commit to youknowone/RustPython that referenced this pull request May 13, 2026
When a `with` body contains a try/except/else whose handler ends with
an open conditional (e.g. `except OSError: if not path.is_symlink(): raise`),
the handler does not unconditionally scope-exit and the synthetic
body-exit NOP introduced by `preserves_finally_entry_nop` is spurious
because control may still fall through to the with cleanup. Guard
against this by anding in `!statements_end_with_open_conditional_fallthrough`
when checking handler scope exits.

Also add the test
`test_with_try_except_else_open_conditional_handler_drops_body_exit_nop`
locking the pattern, and `fobj` to .cspell.dict/python-more.txt so the
prek cspell hook on CI clears for PR RustPython#7870 (used verbatim in the
already-committed test_nested_with_except_same_line_cleanup_threads_trampoline).
youknowone added a commit to youknowone/RustPython that referenced this pull request May 14, 2026
When a `with` body contains a try/except/else whose handler ends with
an open conditional (e.g. `except OSError: if not path.is_symlink(): raise`),
the handler does not unconditionally scope-exit and the synthetic
body-exit NOP introduced by `preserves_finally_entry_nop` is spurious
because control may still fall through to the with cleanup. Guard
against this by anding in `!statements_end_with_open_conditional_fallthrough`
when checking handler scope exits.

Also add the test
`test_with_try_except_else_open_conditional_handler_drops_body_exit_nop`
locking the pattern, and `fobj` to .cspell.dict/python-more.txt so the
prek cspell hook on CI clears for PR RustPython#7870 (used verbatim in the
already-committed test_nested_with_except_same_line_cleanup_threads_trampoline).
@youknowone youknowone marked this pull request as ready for review May 15, 2026 07:50
@youknowone youknowone marked this pull request as draft May 15, 2026 07:51
Three changes that bring optimize_load_fast_borrow closer to CPython's
optimize_load_fast in flowgraph.c:

* ir.rs: split mark_cold into the CPython-style two passes. Phase 1
  propagates "warm" from the entry block, phase 2 propagates "cold" from
  except_handler blocks. Blocks reached by neither phase keep cold=false
  and stay in their original b_next position, matching CPython's handling
  of empty placeholders left by remove_unreachable (e.g. the inner_end of
  a nested try/except whose incoming jumps were re-routed by optimize_cfg).

* ir.rs: in optimize_load_fast_borrow, push the fall-through successor
  only when the current block has a last instruction (is_some_and).
  Empty blocks now terminate fall-through propagation, matching the
  `term != NULL` check in optimize_load_fast.

* compile.rs: add switch_to_new_or_reuse_empty() helper and use it in
  compile_while. The helper reuses the current block when it is empty
  and unlinked, mirroring USE_LABEL absorption in
  cfg_builder_maybe_start_new_block. This stops a stray empty block
  from appearing between e.g. a try/except end_block and the following
  while loop header.

Four codegen tests that depended on the previous fall-through-through-
empty behavior are marked #[ignore] with TODO comments.

Also includes a handful of dictionary entries in .cspell.dict picked up
during the work.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant