Skip to content

LOAD_FAST_BORROW not being used even when safe to do so, if value is live at BB end. #133672

Description

@markshannon

This function

def f(x, y, c):
    return 1 + (x if c else y)

compiles to

  1           RESUME                   0

  2           LOAD_SMALL_INT           1
              LOAD_FAST_BORROW         2 (c)
              TO_BOOL
              POP_JUMP_IF_FALSE        9 (to L1)
              NOT_TAKEN
              LOAD_FAST_BORROW         0 (x)
              BINARY_OP                0 (+)
              RETURN_VALUE
      L1:     LOAD_FAST                1 (y)
              BINARY_OP                0 (+)
              RETURN_VALUE

Note that the load of y uses LOAD_FAST even though LOAD_FAST_BORROW is safe.

This becomes important with virtual iterators as the iterable for the loop is live at BB end.

Linked PRs

Activity

  1. markshannon commented on May 8, 2025

    @markshannon
    MemberAuthor
  2. ljfp commented on May 9, 2025

    @ljfp
    Contributor

    @markshannon I've created this PR to modify the optimization condition in Python/flowgraph.c. The goal is to allow LOAD_FAST to be optimized to LOAD_FAST_BORROW even when the reference is still on the stack at the end of a basic block (i.e., the REF_UNCONSUMED flag is set).

    If I got this right, this should be safe as long as the reference is not killed (SUPPORT_KILLED flag isn't set) and not stored as a local (STORED_AS_LOCAL flag isn't set).

    With these changes, y in your original example now correctly uses LOAD_FAST_BORROW:

    Python 3.15.0a0 (heads/fix-issue-133672:f038e7f472e, May  9 2025, 01:25:23) [GCC 15.1.1 20250425] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> def f(x, y, c):
    ...     return 1 + (x if c else y)
    ... 
    >>> import dis
    >>> dis.dis(f)
      1           RESUME                   0
    
      2           LOAD_SMALL_INT           1
                  LOAD_FAST_BORROW         2 (c)
                  TO_BOOL
                  POP_JUMP_IF_FALSE        9 (to L1)
                  NOT_TAKEN
                  LOAD_FAST_BORROW         0 (x)
                  BINARY_OP                0 (+)
                  RETURN_VALUE
          L1:     LOAD_FAST_BORROW         1 (y)
                  BINARY_OP                0 (+)
                  RETURN_VALUE

    Could you take a look when you have a moment and confirm if this aligns with what you had in mind?

  3. added 2 commits that reference this issue on Apr 25, 2026
  4. added 4 commits that reference this issue on Apr 25, 2026
  5. ljfp commented on Apr 26, 2026

    @ljfp
    Contributor

    @markshannon I opened a new PR for this in #148999.

    I made a few mistakes with the previous PR, so I cleaned up the branch history and opened a replacement PR instead. The new PR should also address the failing tests from the earlier version.

  6. added a commit that references this issue on May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.15bugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)performancePerformance or resource usagetype-featureA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions