◐ Shell
clean mode source ↗

Update opcode, dis from 3.14.2 by youknowone · Pull Request #6869 · RustPython/RustPython

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the file structure and the specific lines mentioned
cd crates/codegen/src && wc -l ir.rs

Repository: RustPython/RustPython

Length of output: 75


🏁 Script executed:

# Examine the code at lines 226-229
sed -n '220,235p' ir.rs

Repository: RustPython/RustPython

Length of output: 113


🏁 Script executed:

# Check the other locations mentioned: 335-345, 360-368, 850-901
echo "=== Lines 335-345 ==="
sed -n '330,350p' ir.rs
echo "=== Lines 360-368 ==="
sed -n '355,373p' ir.rs
echo "=== Lines 850-901 ==="
sed -n '845,905p' ir.rs

Repository: RustPython/RustPython

Length of output: 294


🏁 Script executed:

# Search for lineno_override and related structures to understand the context
rg -B5 -A5 "lineno_override" ir.rs

Repository: RustPython/RustPython

Length of output: 110


🏁 Script executed:

# Search for LineTableLocation struct definition
rg "struct LineTableLocation" ir.rs -A10

Repository: RustPython/RustPython

Length of output: 110


🌐 Web query:

CPython 3.13 3.14 line-0 RESUME instruction linetable encoding

💡 Result:

In CPython 3.13 and 3.14, the source-location info for each bytecode instruction (including the initial RESUME) is stored in code.co_linetable as a stream of location entries. Each entry starts with a header byte 1 cccc lll (MSB set), where cccc is the location kind (0–15) and lll is (length_in_code_units - 1); “code units” are 2-byte units (i.e., one wordcode instruction is length 1). The rest of the entry is kind-specific, using 6-bit-chunk varint/svarint encodings. [1][2]

Why RESUME can show line 0

The start line stored in the first entry is encoded as a delta from co_firstlineno (and subsequent entries delta from the previous start line). So if a code object has co_firstlineno == 0, then RESUME can legitimately decode to line 0 via a delta of 0. [1][2]

(Separately, PEP 626 notes that frames for modules start with f_lineno = 0, and that some bytecodes may be “artificial” and have no meaningful line. Tools should prefer co_lines() / co_positions() rather than assuming the first executable line is co_firstlineno + 1. [3])

The actual encoding you’ll typically see for “line 0 RESUME

Most commonly, RESUME’s entry uses Kind 13 (“No column info”): it stores start/end line deltas but sets columns to None. [1][2]

For a single-instruction RESUME whose decoded line is 0 (i.e., first entry, start-line delta = 0, end-line delta = 0):

  • header byte: kind=13, length=1 → 1 1101 000 = 0xE8
  • start-line delta (svarint 0 → varint 0) → 0x00
  • end-line delta (Δ from start line, 0) → 0x00

So the entry bytes are:

E8 00 00 (hex) [1][2]

If instead CPython marks that instruction range as having no location at all (Kind 15 “No location”), then for length=1 the entry is just:

F8 (hex) [1][2]

Note on “RESUME has the def-line number”

For functions, co_firstlineno is the line with the def statement, so the first entry’s delta-0 convention makes RESUME map to the function definition line, which is a behavior change that has affected some tools. [2][4]

Sources: CPython 3.13 Objects/locations.md (format reference) [1]; CPython 3.14 InternalDocs/code_objects.md (same format, moved docs) [2]; PEP 626 (line-number model, module line 0, “artificial” bytecodes) [3]; python.org discussion about RESUME line numbering changes [4].


🌐 Web query:

CPython linetable format line 0 no-line instructions bytecode

💡 Result:

In modern CPython (3.10+), line/position info is no longer “every instruction has a real source line”. The bytecode→source mapping is stored in the code object’s line table (co_linetable) and exposed via code.co_lines(), which can yield ranges whose line is None (“no line number”) for artificial / bookkeeping bytecode. This is explicitly part of the PEP 626 model: some bytecodes “must be marked as artificial, and not have a meaningful line number”, and the iterator’s line can be None. [1]

Internally, when CPython decodes the line table, it represents “no line number” as a negative line (range.line < 0). [1] In the older (3.10-era) internal notes for the delta-encoded line table, there’s also an explicit sentinel for “no line number” (a special line-delta value). [2]

So if you’re seeing line 0 attached to certain instructions, that’s almost always a tooling/formatting artifact where “no line” (None / <0) got coerced to 0 for display or storage. Typical examples of bytecode that may have “no-line” (or otherwise surprising) locations are interpreter/setup opcodes like RESUME (and related prologue opcodes around generators/coroutines), which has been discussed as a behavior change users notice when enumerating covered lines. [3]

Practical guidance:

  • Treat “line 0” as “no line info”, not a real source line.
  • Prefer code.co_lines() (line can be None) per PEP 626, rather than reverse-engineering co_linetable. [1]
  • If you need per-instruction positions, use dis.get_instructions() / dis.dis() (these are built to follow the current semantics). [4]

Sources:
[1] PEP 626 (Precise line numbers; co_lines() yields line or None, and internal decode treats <0 as no-line) https://peps.python.org/pep-0626/
[2] CPython Objects/lnotab_notes.txt (sentinel encoding for “no line number”) https://chromium.googlesource.com/external/github.com/python/cpython/%2B/refs/tags/v3.10.0b3/Objects/lnotab_notes.txt
[3] Discussion of RESUME line-number behavior in 3.11 https://discuss.python.org/t/the-resume-opcode-has-the-same-line-number-as-the-function-definition-and-breaks-previous-behaviour/21972
[4] dis module docs (3.11+) https://docs.python.org/3.11/library/dis.html


🏁 Script executed:

git ls-files | grep ir.rs

Repository: RustPython/RustPython

Length of output: 89


🏁 Script executed:

# Once we have the path, read the file at specified lines
git ls-files | grep ir.rs | head -1 | xargs -I {} sh -c 'echo "Lines 220-235:"; sed -n "220,235p" {}; echo -e "\nLines 330-350:"; sed -n "330,350p" {}; echo -e "\nLines 355-373:"; sed -n "355,373p" {}; echo -e "\nLines 845-905:"; sed -n "845,905p" {}'

Repository: RustPython/RustPython

Length of output: 4652


🏁 Script executed:

# Search for all LineTableLocation creations in the file
rg "LineTableLocation" crates/codegen/src/ir.rs -B3 -A3

Repository: RustPython/RustPython

Length of output: 1069


🏁 Script executed:

# Look for the struct definition
rg "struct LineTableLocation" crates/codegen/src/ -A5

Repository: RustPython/RustPython

Length of output: 309


🏁 Script executed:

# Search for lineno_override usage to understand the full context
rg "lineno_override" crates/codegen/src/ir.rs -B10 -A5

Repository: RustPython/RustPython

Length of output: 1512


🏁 Script executed:

# Check lines 360-368 more carefully
sed -n '355,375p' crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 696


🏁 Script executed:

# Check if there are other LineTableLocation creations
rg "LineTableLocation {" crates/codegen/src/ir.rs -B1 -A5

Repository: RustPython/RustPython

Length of output: 166


🏁 Script executed:

# Verify the generate_linetable function handles these values
sed -n '850,920p' crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 2870


🏁 Script executed:

# Search for all LineTableLocation creations without regex special chars
grep -n "let lt_loc = LineTableLocation" crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 121


🏁 Script executed:

# Also search for direct LineTableLocation struct instantiations
grep -n "LineTableLocation {" crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 151


🏁 Script executed:

# Let's see what's at lines 360-368 to understand "Also applies to"
sed -n '355,375p' crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 696


🏁 Script executed:

# Check if there are multiple code blocks creating LineTableLocation with same pattern
sed -n '300,330p' crates/codegen/src/ir.rs
sed -n '340,370p' crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 2783


🏁 Script executed:

# Let me verify there's only one LineTableLocation creation by searching more carefully
grep -n "LineTableLocation {" crates/codegen/src/ir.rs -A6

Repository: RustPython/RustPython

Length of output: 677


🏁 Script executed:

# Check if the code at 226-229 is actually the right location by looking at context
sed -n '220,245p' crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 1066


🏁 Script executed:

# Verify the lineno_override handling one more time
sed -n '330,350p' crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 1302


🏁 Script executed:

# Count total LineTableLocation creations
grep -c "let lt_loc = LineTableLocation" crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 66


🏁 Script executed:

# Verify the line numbers in the "Also applies to" section - check 360-368
sed -n '360,375p' crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 583


🏁 Script executed:

# Let me check around line 850-901 to understand what that refers to
sed -n '848,855p' crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 310


🏁 Script executed:

# Verify the issue: when lineno_override = Some(0), what happens in generate_linetable?
# Check if line_delta could be 0 but end_line_delta could be non-zero
sed -n '870,920p' crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 2265


🏁 Script executed:

# Let me understand the "Also applies to" references more carefully
# Check what's at the line ranges mentioned
echo "=== Checking 335-345 range ===" 
sed -n '335,345p' crates/codegen/src/ir.rs

echo "=== Checking 360-368 range ==="
sed -n '360,368p' crates/codegen/src/ir.rs

echo "=== Checking 850-901 context - is this generate_linetable? ==="
sed -n '845,860p' crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 1621


🏁 Script executed:

# Verify the fix would work correctly by checking generate_linetable logic
# When col=0, end_col=0, what happens?
sed -n '895,950p' crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 3063


🏁 Script executed:

# Let me check the actual line-0 RESUME context by searching for where lineno_override is set
rg "lineno_override\s*=" crates/codegen/src/ -B2 -A2

Repository: RustPython/RustPython

Length of output: 392


🏁 Script executed:

# Verify the concern about end_line_delta when lineno_override=Some(0)
# If lineno_override=Some(0) but end_location.line is non-zero, end_line_delta would be non-zero
# This would not fall into the "line_delta == 0 && end_line_delta == 0" path
# Let's check what happens in the else path
sed -n '910,950p' crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 2306


🏁 Script executed:

# Let me check the complete flow for line-0 case
# First, verify module scope sets lineno_override
sed -n '1,50p' crates/codegen/src/compile.rs | grep -A5 "Module scope"

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Check the line-0 flow: what happens when prev_line=first_line and line=0
sed -n '930,960p' crates/codegen/src/ir.rs

Repository: RustPython/RustPython

Length of output: 1703


🏁 Script executed:

# Let me verify one more time that the fix would be appropriate by checking
# if there are tests or comments about line-0 RESUME semantics
rg "RESUME\|line.*0\|line-0" crates/codegen/src/ir.rs | head -20

Repository: RustPython/RustPython

Length of output: 47


Override end_line and columns when lineno_override is set to 0.

When lineno_override = Some(0) is set for module-scope RESUME, the code currently overrides only line to 0 while keeping end_line and columns from the actual source location. Per CPython 3.13+ semantics, line 0 represents "no line" (artificial instructions), and such entries should have no meaningful column information. The current code creates a 0→first_line span with real columns, which violates CPython's linetable encoding (Kind 13 "NoColumns" expects no column data for line-0 entries).