Skip to content

Add has_* methods to opcode enums#7818

Closed
ShaharNaveh wants to merge 2 commits into
RustPython:mainfrom
ShaharNaveh:opcode-flags
Closed

Add has_* methods to opcode enums#7818
ShaharNaveh wants to merge 2 commits into
RustPython:mainfrom
ShaharNaveh:opcode-flags

Conversation

@ShaharNaveh
Copy link
Copy Markdown
Contributor

@ShaharNaveh ShaharNaveh commented May 10, 2026

Extracted from #7797

Summary by CodeRabbit

  • Refactor
    • Improved bytecode opcode/pseudo-opcode capability checks for more consistent and reliable behavior.
    • Streamlined Python-facing opcode helpers to a uniform approach, reducing duplication and improving maintainability.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: c00767f3-6b24-4865-a081-49a71f9fe8e2

📥 Commits

Reviewing files that changed from the base of the PR and between d946a58 and 17efe8e.

📒 Files selected for processing (1)
  • crates/stdlib/src/_opcode.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/stdlib/src/_opcode.rs

📝 Walkthrough

Walkthrough

This PR adds opcode capability query helpers to the bytecode instruction layer. New predicates (has_const, has_free, has_jump, has_local, has_name) and block classification (is_block_push) are introduced on both Opcode and PseudoOpcode. These are exposed through Instruction/PseudoInstruction delegators and unified via a new either_real_pseudo! macro for AnyInstruction/AnyOpcode dispatch. Python stdlib _opcode helpers are refactored to delegate to these new abstractions.

Changes

Bytecode Instruction Opcode Helpers

Layer / File(s) Summary
Opcode Capability Predicates
crates/compiler-core/src/bytecode/instruction.rs
Opcode and PseudoOpcode types gain const methods has_const, has_free, has_jump, has_local, has_name, and is_block_push. Real opcodes return false for is_block_push; pseudo opcodes implement it for Setup* variants.
Instruction Delegators
crates/compiler-core/src/bytecode/instruction.rs
Instruction and PseudoInstruction gain is_block_push(&self) methods that delegate to their underlying Opcode and PseudoOpcode.
AnyInstruction/AnyOpcode Macro Dispatch
crates/compiler-core/src/bytecode/instruction.rs
New either_real_pseudo! macro unifies InstructionMetadata dispatch for AnyInstruction. AnyOpcode gains const query methods and is_block_push, dispatching to real or pseudo variants. AnyInstruction::is_block_push receiver changed from self to &self.
Python Stdlib Refactoring
crates/stdlib/src/_opcode.rs
Module imports change to use AnyOpcode. Functions has_const, has_name, has_jump, has_free, has_local, has_exc refactored from hardcoded matches! lists to uniform delegation via `try_from_i32(opcode).map_or(false,

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • RustPython/RustPython#7757: Modifies AnyInstruction opcode-related helpers and refactors instruction/opcode dispatch, directly overlapping.
  • RustPython/RustPython#7711: Changes _opcode pyfunctions' opcode-match logic; closely related to stdlib helper refactors here.
  • RustPython/RustPython#7585: Modifies bytecode instruction metadata and pseudo-instruction behavior across the same file(s).

Suggested reviewers

  • coolreader18
  • youknowone

Poem

🐇 I nibble bytes and hum a tune,

has_const, has_free beneath the moon;
either_real_pseudo! ties the knot,
Opcodes sing, pseudo-ops trot;
A little hop for cleaner thought.

🚥 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 'Add has_* methods to opcode enums' accurately summarizes the main change: introducing new opcode-flag query helpers (has_const, has_free, has_jump, has_local, has_name) to both Opcode and PseudoOpcode.
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

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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/compiler-core/src/bytecode/instruction.rs`:
- Around line 1051-1074: The has_jump() method is missing two specialized opcode
variants; update the matches! list inside pub const fn has_jump(self) -> bool
(in instruction.rs) to also include ForIterGen and SendGen so they map
consistently to the base opcodes ForIter and Send (similar to the existing
ForIterList/ForIterRange/ForIterTuple and JumpBackwardJit/JumpBackwardNoJit
entries); simply add ForIterGen and SendGen to the list of matched variants in
has_jump().
- Around line 1042-1049: The has_free() method currently matches DeleteDeref,
LoadFromDictOrDeref, MakeCell, and StoreDeref; update it to match CPython's
HAS_FREE_FLAG by including LoadDeref and removing LoadFromDictOrDeref and
MakeCell. Edit the matches expression inside the pub const fn has_free(&self) to
only include Self::DeleteDeref | Self::LoadDeref | Self::StoreDeref, ensuring
the function name has_free and the opcode variants DeleteDeref, LoadDeref,
StoreDeref are the referenced symbols to change.
🪄 Autofix (Beta)

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: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 2a215c26-cd0a-4398-8445-1384ce64a21e

📥 Commits

Reviewing files that changed from the base of the PR and between 108461f and d946a58.

📒 Files selected for processing (2)
  • crates/compiler-core/src/bytecode/instruction.rs
  • crates/stdlib/src/_opcode.rs

Comment on lines +1042 to +1049
/// Does this opcode have 'HAS_FREE_FLAG' set.
#[must_use]
pub const fn has_free(self) -> bool {
matches!(
self,
Self::DeleteDeref | Self::LoadFromDictOrDeref | Self::MakeCell | Self::StoreDeref
)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

CPython 3.14 HAS_FREE_FLAG opcode classification which opcodes should be included

💡 Result:

For CPython 3.14, the opcodes marked with the internal opcode-metadata flag HAS_FREE_FLAG are the following: 1) DELETE_DEREF 2) LOAD_CLOSURE 3) LOAD_DEREF 4) STORE_DEREF Evidence: CPython defines HAS_FREE_FLAG in its internal opcode metadata and associates it with the above opcode entries (e.g., DELETE_DEREF is shown with HAS_ARG_FLAG | HAS_FREE_FLAG... in the opcode metadata table) [1]. Related context: the disassembler’s public dis.hasfree set corresponds to “bytecodes that access a free (closure) variable” [2][3], i.e., the closure/free-variable access opcodes above.

Citations:


🏁 Script executed:

cd crates/compiler-core/src/bytecode && grep -n "enum Opcode\|pub const fn has_free\|LoadDeref\|LoadFromDictOrDeref\|MakeCell\|CopyFreeVars" instruction.rs | head -50

Repository: RustPython/RustPython

Length of output: 688


🏁 Script executed:

# Get the specific lines mentioned in the review
sed -n '1042,1049p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 320


🏁 Script executed:

# Check for LoadDeref definition
sed -n '280,300p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 743


🏁 Script executed:

# Check for CopyFreeVars definition
sed -n '220,230p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 359


🏁 Script executed:

# Search for all occurrences of these opcodes in the file
rg -n "LoadDeref|LoadFromDictOrDeref|MakeCell|CopyFreeVars|DeleteDeref|StoreDeref" crates/compiler-core/src/bytecode/instruction.rs | head -30

Repository: RustPython/RustPython

Length of output: 677


🏁 Script executed:

sed -n '1080,1090p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 475


🏁 Script executed:

# Check for docstrings/comments on LoadFromDictOrDeref and MakeCell
sed -n '308,315p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 367


🏁 Script executed:

sed -n '329,340p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 405


🏁 Script executed:

# Search for any usage or discussion of has_free
rg -B2 -A2 "has_free" crates/compiler-core/src/bytecode/instruction.rs | head -40

Repository: RustPython/RustPython

Length of output: 435


🏁 Script executed:

# Get full context around line 1044 to understand the structure
sed -n '1040,1055p' crates/compiler-core/src/bytecode/instruction.rs

Repository: RustPython/RustPython

Length of output: 473


🏁 Script executed:

# Let's also check if there are any comments or documentation about why these opcodes are in has_free
rg -B5 "LoadFromDictOrDeref|MakeCell" crates/compiler-core/src/bytecode/instruction.rs | head -50

Repository: RustPython/RustPython

Length of output: 1358


Add LoadDeref to has_free implementation and remove non-standard opcodes.

The has_free method is missing LoadDeref (line 289), which should be included per CPython's HAS_FREE_FLAG classification. CPython marks DELETE_DEREF, LOAD_DEREF, and STORE_DEREF as having this flag; rustpython correctly includes the first and third but omits the second.

Additionally, LoadFromDictOrDeref and MakeCell should not be in this implementation, as they do not have HAS_FREE_FLAG in CPython's opcode metadata.

Update the matches expression to:

Self::DeleteDeref | Self::LoadDeref | Self::StoreDeref
🤖 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/compiler-core/src/bytecode/instruction.rs` around lines 1042 - 1049,
The has_free() method currently matches DeleteDeref, LoadFromDictOrDeref,
MakeCell, and StoreDeref; update it to match CPython's HAS_FREE_FLAG by
including LoadDeref and removing LoadFromDictOrDeref and MakeCell. Edit the
matches expression inside the pub const fn has_free(&self) to only include
Self::DeleteDeref | Self::LoadDeref | Self::StoreDeref, ensuring the function
name has_free and the opcode variants DeleteDeref, LoadDeref, StoreDeref are the
referenced symbols to change.

Comment on lines +1051 to +1074
/// Does this opcode have 'HAS_JUMP_FLAG' set.
#[must_use]
pub const fn has_jump(self) -> bool {
matches!(
self,
Self::EndAsyncFor
| Self::ForIter
| Self::JumpBackward
| Self::JumpBackwardNoInterrupt
| Self::JumpForward
| Self::PopJumpIfFalse
| Self::PopJumpIfNone
| Self::PopJumpIfNotNone
| Self::PopJumpIfTrue
| Self::Send
| Self::ForIterList
| Self::ForIterRange
| Self::ForIterTuple
| Self::JumpBackwardJit
| Self::JumpBackwardNoJit
| Self::InstrumentedForIter
| Self::InstrumentedEndAsyncFor
)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for all ForIter* and Send* opcode definitions to verify completeness
echo "=== ForIter variants ==="
rg -n "^\s+(ForIter\w*)\s*=" crates/compiler-core/src/bytecode/instruction.rs

echo -e "\n=== Send variants ==="
rg -n "^\s+(Send\w*)\s*=" crates/compiler-core/src/bytecode/instruction.rs

echo -e "\n=== Opcodes in has_jump ==="
rg -A50 "pub const fn has_jump\(self\) -> bool" crates/compiler-core/src/bytecode/instruction.rs | rg "Self::" | head -20

Repository: RustPython/RustPython

Length of output: 1150


🏁 Script executed:

# Check CPython's opcode definitions to see if ForIterGen and SendGen have HAS_JUMP_FLAG
curl -s https://raw.githubusercontent.com/python/cpython/main/Include/opcode.h | grep -A 2 -B 2 "FOR_ITER_GEN\|SEND_GEN" | head -30

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Also check if there's any documentation in RustPython about this
rg -B5 -A5 "ForIterGen|SendGen" crates/compiler-core/src/bytecode/instruction.rs | head -40

Repository: RustPython/RustPython

Length of output: 2090


🏁 Script executed:

# Check if there's a pattern or comment explaining why Gen variants are excluded
rg -B10 "pub const fn has_jump" crates/compiler-core/src/bytecode/instruction.rs | head -20

Repository: RustPython/RustPython

Length of output: 519


🏁 Script executed:

# Let's check CPython's Python/opcode_ids.h directly for the actual opcode definitions
python3 <<'EOF'
import urllib.request
import json

# Fetch CPython's opcode definitions
url = "https://raw.githubusercontent.com/python/cpython/main/Python/opcode_ids.h"
try:
    with urllib.request.urlopen(url, timeout=5) as response:
        content = response.read().decode('utf-8')
        # Search for FOR_ITER_GEN and SEND_GEN definitions
        lines = content.split('\n')
        for i, line in enumerate(lines):
            if 'FOR_ITER_GEN' in line or 'SEND_GEN' in line:
                # Print context around the definition
                start = max(0, i - 2)
                end = min(len(lines), i + 3)
                for j in range(start, end):
                    print(f"{j}: {lines[j]}")
                print("---")
except Exception as e:
    print(f"Error fetching CPython opcode definitions: {e}")

# Also check core/ceval.c for HAS_JUMP_FLAG definitions
url2 = "https://raw.githubusercontent.com/python/cpython/main/Python/ceval.c"
try:
    with urllib.request.urlopen(url2, timeout=5) as response:
        content = response.read().decode('utf-8')
        lines = content.split('\n')
        for i, line in enumerate(lines):
            if 'FOR_ITER_GEN' in line or 'SEND_GEN' in line:
                # Print context
                start = max(0, i - 2)
                end = min(len(lines), i + 3)
                for j in range(start, end):
                    print(f"{j}: {lines[j]}")
                print("---")
except Exception as e:
    print(f"Error fetching CPython ceval.c: {e}")
EOF

Repository: RustPython/RustPython

Length of output: 396


🏁 Script executed:

# Let's check if there are any comments in the RustPython code about jump flags or specialized variants
rg -B10 -A10 "has_jump" crates/compiler-core/src/bytecode/instruction.rs | grep -A30 "pub const fn has_jump"

Repository: RustPython/RustPython

Length of output: 1418


🏁 Script executed:

# Check if there's any test or documentation about which opcodes should have jump flag
fd instruction tests test | head -5

Repository: RustPython/RustPython

Length of output: 209


🏁 Script executed:

# Look for any git history or comments about ForIterGen/SendGen jump behavior
rg "ForIterGen|SendGen" crates/compiler-core/src -B3 -A3

Repository: RustPython/RustPython

Length of output: 4344


Include ForIterGen and SendGen in the has_jump method.

The implementation includes specialized variants like ForIterList, ForIterRange, ForIterTuple, JumpBackwardJit, and JumpBackwardNoJit, but omits:

  • ForIterGen (line 448) — maps to base opcode ForIter
  • SendGen (line 197) — maps to base opcode Send

Since their base opcodes (ForIter and Send) are included, and other specialized variants are also present, these should be added for consistency.

🤖 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/compiler-core/src/bytecode/instruction.rs` around lines 1051 - 1074,
The has_jump() method is missing two specialized opcode variants; update the
matches! list inside pub const fn has_jump(self) -> bool (in instruction.rs) to
also include ForIterGen and SendGen so they map consistently to the base opcodes
ForIter and Send (similar to the existing ForIterList/ForIterRange/ForIterTuple
and JumpBackwardJit/JumpBackwardNoJit entries); simply add ForIterGen and
SendGen to the list of matched variants in has_jump().

@ShaharNaveh
Copy link
Copy Markdown
Contributor Author

closed by #7797

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