Repository navigation
LOAD_FAST is not always converted to LOAD_FAST_BORROW in a basicblock #145629
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Mar 7, 2026 - addedinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-featureA feature request or enhancementA feature request or enhancementand removedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Mar 7, 2026 - added a parent issue
on Mar 7, 2026 - addedperformancePerformance or resource usagePerformance or resource usage
on Mar 7, 2026 I investigated in
in optimize_load_fast()and found that LOAD_FAST branch is marked REF_UNCONSUMED during reference stack cleanup, which prevents the LOAD_FAST -> LOAD_FAST_BORROW optimization.From debugging i found that it appears that the refrence remains on the simulated stack when the block ends with RETURN_VALUE, causing it to be incorrectly classified.
Debug line added:
printf("[DEBUG] Marking instr %d as REF_UNCONSUMED (block size %d)\n", r.instr, block->b_iused);
Terminal output:
[DEBUG] Marking instr 0 as REF_UNCONSUMED (block size 1)I want to confirm that whether the refrence stack should treat RETURN_VALUE as consuming the top stack value during this analysis step.
@anand-official When I tested your solution, it did not convert the
LOAD_FASTinto aLOAD_FAST_BORROWfrom the first example in the original issue.Would you like me to prepare a PR with this fix?
No.
I want to confirm that whether the refrence stack should treat RETURN_VALUE as consuming the top stack value during this analysis step.
@VanshAgarwal24036
RETURN_VALUEshould consume the stack variable. As such, I am not exactly sure about the root cause of this issue. Unless I am missing something, the issue seems very subtle.I would appreciate any guidance from people who understand the byte code optimizer better than I do.
Reacted by (James) Kanin KearpimyThank you for the detailed bug report. I’ve been looking into this as well and wanted to share my findings and a question.
What I found
The first LOAD_FAST (instruction 0) gets marked REF_UNCONSUMED in the cleanup loop (lines 3006–3011).This happens because the reference stack still contains an entry when the block ends with RETURN_VALUE. It looks like the simulation never pops for RETURN_VALUE in the main loop.
Why the simple fix didn’t work
I tried popping the reference stack for RETURN_VALUE before cleanup, but that alone doesn’t enable the optimization because the REF_LAST_USE flag for that instruction was already missed in the main loop. We need to handle the pop inside the main loop, just like for BINARY_OP or COMPARE_OP.The challenge
RETURN_VALUE consumes the top stack value, but that value may or may not be a reference. The current reference stack only tracks references, not all values. To pop correctly, we’d need to know whether the top value is a reference – essentially, we’d need a parallel “value type” stack or a way to deduce it from the instruction that produced it.Question for core devs
@markshannon, @brandtbucher, @iritkatriel—how is the reference simulation supposed to handle RETURN_VALUE? Is there a known limitation or a simpler way to determine if the top value is a reference without a full value stack? I’d be happy to help implement a fix once we agree on the right approach.I also added debug prints while investigating this and saw that the instruction corresponding to the
LOAD_FASTin theelsebranch gets markedREF_UNCONSUMEDduring the cleanup loop.For example:
[DEBUG] Marking instr 0 as REF_UNCONSUMED (block size 1)This seems to correspond to the
LOAD_FAST bbeforeRETURN_VALUE.So the reference stack still contains the entry when the block ends with
RETURN_VALUE, which then gets marked asREF_UNCONSUMED.
Bug report
Bug description:
Originally addressed in Issue #144388, the bytecode compiler does not always convert a
LOAD_FASTinstruction into aLOAD_FAST_BORROW. For example, the following python code, which gets compiled into only onebasicblock, has aLOAD_FASTwhen it could have aLOAD_FAST_BORROW:Byte code
3 RESUME 0 4 LOAD_FAST_BORROW_LOAD_FAST_BORROW 1 (a, b) COMPARE_OP 18 (bool(<)) POP_JUMP_IF_FALSE 3 (to L1) NOT_TAKEN LOAD_FAST_BORROW 0 (a) RETURN_VALUE L1: LOAD_FAST 1 (b) RETURN_VALUEIn contrast, the following expanded version of
fdoes convert allLOAD_FASTinstructions intoLOAD_FAST_BORROWand has three different basic blocks:Byte code
3 RESUME 0 4 LOAD_FAST_BORROW_LOAD_FAST_BORROW 1 (a, b) COMPARE_OP 18 (bool(<)) POP_JUMP_IF_FALSE 3 (to L1) NOT_TAKEN 5 LOAD_FAST_BORROW 0 (a) RETURN_VALUE 7 L1: LOAD_FAST_BORROW 1 (b) RETURN_VALUEThe function of interest is
optimize_load_fastinPython/flowgraph.c. I have tried to fix this bug but have not been able to yet. All I have found so far is that theLOAD_FASTinstruction is misclassified asREF_UNCONSUMEDhere for the case of functionf:cpython/Python/flowgraph.c
Lines 3006 to 3011 in 149c465
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs