X Tutup
Skip to content

gh-144681: Fix JIT trace builder assertion failure when conditional branch jump target coincides with fallthrough target#144742

Merged
Fidget-Spinner merged 7 commits intopython:mainfrom
cocolato:fix-gh-144681
Mar 10, 2026
Merged

gh-144681: Fix JIT trace builder assertion failure when conditional branch jump target coincides with fallthrough target#144742
Fidget-Spinner merged 7 commits intopython:mainfrom
cocolato:fix-gh-144681

Conversation

@cocolato
Copy link
Contributor

@cocolato cocolato commented Feb 12, 2026

When the JIT trace builder translates POP_JUMP_IF_TRUE instructions, it determines the branch direction by comparing next_instr (where execution actually went) against computed_jump_instr (the jump target address). However, when oparg equals 1 and the instruction is followed by NOT_TAKEN, the computed jump target and the computed fallthrough target are the same address:

computed_next_instr = target + 2 + 1 (skip NOT_TAKEN) = target + 3
computed_jump_instr = target + 2 + 1 (oparg)

In this minimal reproduction case:

def trigger():
    _ = [x for x in range(200) if [].append(x) or True]

for i in range(300):
    trigger()

print("we finished")

which produces:

...
        POP_JUMP_IF_TRUE         1 (to L4)
L3:     NOT_TAKEN
L4:     LOAD_FAST_BORROW         0 (x)
        LIST_APPEND              3
        JUMP_BACKWARD           30 (to L2)
...

the address comparison computed_jump_instr == next_instr is always true regardless of which branch was actually taken, causing the assertion jump_happened == (target_instr[1].cache & 1) failed when the branch was not taken.

So we should fall back to the RECORD_BRANCH_TAKEN cache bit (target_instr[1].cache & 1) to determine the branch direction, since address comparison is ambiguous.

@devdanzin
Copy link
Member

Related: #141797

Copy link
Member

@Fidget-Spinner Fidget-Spinner left a comment

Choose a reason for hiding this comment

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

Thanks

Copy link
Member

@markshannon markshannon left a comment

Choose a reason for hiding this comment

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

Thanks for finding the root problem and coming up with a fix. This can be simplified by treating target_instr[1].cache & 1 as the source of truth for branches.

_Py_CODEUNIT *computed_jump_instr = computed_next_instr_without_modifiers + oparg;
assert(next_instr == computed_next_instr || next_instr == computed_jump_instr);
int jump_happened = computed_jump_instr == next_instr;
int jump_happened;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
int jump_happened;
int jump_happened = target_instr[1].cache & 1;

The direction of the jump is always recorded.

} else {
jump_happened = computed_jump_instr == next_instr;
}
assert(jump_happened == (target_instr[1].cache & 1));
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
assert(jump_happened == (target_instr[1].cache & 1));
assert(jump_happened ? (next_instr == computed_jump_instr) : (next_instr == computed_next_instr));

assert(next_instr == computed_next_instr || next_instr == computed_jump_instr);
int jump_happened = computed_jump_instr == next_instr;
int jump_happened;
if (computed_next_instr == computed_jump_instr) {
Copy link
Member

Choose a reason for hiding this comment

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

Then there is no need to change behavior depending on how long the branch is.

@bedevere-app
Copy link

bedevere-app bot commented Mar 9, 2026

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

And if you don't make the requested changes, you will be poked with soft cushions!

@cocolato
Copy link
Contributor Author

cocolato commented Mar 9, 2026

Thanks for review! I have made the requested changes; please review again.

@bedevere-app
Copy link

bedevere-app bot commented Mar 9, 2026

Thanks for making the requested changes!

@markshannon, @Fidget-Spinner: please review the changes made to this pull request.

@cocolato
Copy link
Contributor Author

cocolato commented Mar 9, 2026

The failed test appears unrelated to the current PR. It seems to have been introduced here: #145279

@markshannon
Copy link
Member

Let's see if updating the branch fixes it

@Fidget-Spinner Fidget-Spinner merged commit 66eafc9 into python:main Mar 10, 2026
68 checks passed
@bedevere-bot
Copy link

⚠️⚠️⚠️ Buildbot failure ⚠️⚠️⚠️

Hi! The buildbot AMD64 Fedora Stable Refleaks 3.x (tier-1) has failed when building commit 66eafc9.

What do you need to do:

  1. Don't panic.
  2. Check the buildbot page in the devguide if you don't know what the buildbots are or how they work.
  3. Go to the page of the buildbot that failed (https://buildbot.python.org/#/builders/320/builds/3803) and take a look at the build logs.
  4. Check if the failure is related to this commit (66eafc9) or if it is a false positive.
  5. If the failure is related to this commit, please, reflect that on the issue and make a new Pull Request with a fix.

You can take a look at the buildbot page here:

https://buildbot.python.org/#/builders/320/builds/3803

Failed tests:

  • test.test_multiprocessing_fork.test_processes

Failed subtests:

  • test_interrupt - test.test_multiprocessing_fork.test_processes.WithProcessesTestProcess.test_interrupt

Summary of the results of the build (if available):

==

Click to see traceback logs
Traceback (most recent call last):
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/multiprocessing/process.py", line 320, in _bootstrap
    self.run()
    ~~~~~~~~^^
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/test/_test_multiprocessing.py", line 525, in _sleep_some_event
    time.sleep(100)
    ~~~~~~~~~~^^^^^
KeyboardInterrupt
k


Traceback (most recent call last):
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/contextlib.py", line 85, in inner
    return func(*args, **kwds)
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/test/_test_multiprocessing.py", line 597, in test_interrupt
    exitcode = self._kill_process(multiprocessing.Process.interrupt)
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/contextlib.py", line 85, in inner
    return func(*args, **kwds)
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/test/_test_multiprocessing.py", line 578, in _kill_process
    self.assertEqual(join(), None)
                     ~~~~^^
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/test/_test_multiprocessing.py", line 251, in __call__
    return self.func(*args, **kwds)
           ~~~~~~~~~^^^^^^^^^^^^^^^
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/multiprocessing/process.py", line 156, in join
    res = self._popen.wait(timeout)
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/multiprocessing/popen_fork.py", line 44, in wait
    return self.poll(os.WNOHANG if timeout == 0.0 else 0)
           ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/multiprocessing/popen_fork.py", line 28, in poll
    pid, sts = os.waitpid(self.pid, flag)
               ~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/test/_test_multiprocessing.py", line 574, in handler
    raise RuntimeError('join took too long: %s' % p)
RuntimeError: join took too long: <Process name='Process-1' pid=3582947 parent=3582945 started daemon>


Traceback (most recent call last):
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/contextlib.py", line 85, in inner
    return func(*args, **kwds)
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/test/_test_multiprocessing.py", line 597, in test_interrupt
    exitcode = self._kill_process(multiprocessing.Process.interrupt)
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/contextlib.py", line 85, in inner
    return func(*args, **kwds)
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/test/_test_multiprocessing.py", line 578, in _kill_process
    self.assertEqual(join(), None)
                     ~~~~^^
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/test/_test_multiprocessing.py", line 251, in __call__
    return self.func(*args, **kwds)
           ~~~~~~~~~^^^^^^^^^^^^^^^
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/multiprocessing/process.py", line 156, in join
    res = self._popen.wait(timeout)
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/multiprocessing/popen_fork.py", line 44, in wait
    return self.poll(os.WNOHANG if timeout == 0.0 else 0)
           ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/multiprocessing/popen_fork.py", line 28, in poll
    pid, sts = os.waitpid(self.pid, flag)
               ~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "/home/buildbot-worker/cstratak-fedora-stable-x86_64/3.x.cstratak-fedora-stable-x86_64.refleak/build/Lib/test/_test_multiprocessing.py", line 574, in handler
    raise RuntimeError('join took too long: %s' % p)
RuntimeError: join took too long: <Process name='Process-929' pid=3539619 parent=3533096 started daemon>

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.

5 participants

X Tutup