Skip to content

gh-149481: skip FOR_ITER inline specialization for Python __next__ - #149491

Merged
savannahostrowski merged 5 commits into
python:mainfrom
NekoAsakura:gh-149481/skip-slot-iternext
May 7, 2026
Merged

gh-149481: skip FOR_ITER inline specialization for Python __next__#149491
savannahostrowski merged 5 commits into
python:mainfrom
NekoAsakura:gh-149481/skip-slot-iternext

Conversation

@NekoAsakura

@NekoAsakuraNekoAsakura commented May 7, 2026

Copy link
Copy Markdown
Member

Thanks for the careful bisect and the empirical fix from @savannahostrowski. ❤️

Benchmark
BenchmarkunpatchedpatchedΔ
for_iter_dict_items185.8 ms ± 1.6 ms187.0 ms ± 2.8 ms+0.68%
for_iter_dict_keys154.1 ms ± 2.1 ms153.9 ms ± 2.6 ms-0.11%
for_iter_dict_values148.2 ms ± 1.0 ms146.9 ms ± 2.8 ms-0.85%
for_iter_set179.1 ms ± 1.9 ms179.0 ms ± 2.2 ms-0.07%
for_iter_reversed136.6 ms ± 1.2 ms136.0 ms ± 1.9 ms-0.47%
for_iter_enumerate201.1 ms ± 2.9 ms201.1 ms ± 2.4 ms-0.04%
for_iter_zip230.6 ms ± 1.2 ms227.9 ms ± 1.3 ms-1.15%
for_iter_list114.2 ms ± 1.8 ms115.6 ms ± 2.2 ms+1.26%
for_iter_tuple104.4 ms ± 1.5 ms104.7 ms ± 1.7 ms+0.35%
for_iter_range137.6 ms ± 2.5 ms137.8 ms ± 1.9 ms+0.13%
Geometric mean-0.03%
Root cause analyse (from opus 4.7)

When __next__ is Python-defined, tp_iternext is the generic slot_tp_iternext slot wrapper that re-enters the eval loop via vectorcall_method. Calling it through the new _ITER_NEXT_INLINE (which captures tp_iternext as a baked-in operand and lives in a tight _JUMP_TO_TOP loop) is correct functionally, but each new outer-iteration class causes _GUARD_TYPE_ITER to fail and warm a side-exit. After ~SIDE_EXIT_INITIAL_VALUE (4000) hits at each side-exit, a side trace forms there; _EXIT_TRACE jumps trace-to-trace via TIER2_TO_TIER2, which doesn't re-check the eval-breaker. With MAX_CHAIN_DEPTH=4 traces possible, the chain accumulates ~4×4000+4002 ≈ 20k inner hits before the system spirals into a tier-2-only loop with no signal checkpoints — matching the observed exact threshold. The pre-PR _FOR_ITER_TIER_TWO path goes through _PyForIter_VirtualIteratorNext, which uses an indirect dispatch on Py_TYPE(iter_o)->tp_iternext and does not produce a per-type guard, so no side-trace storm forms. Falling back to that path for slot iterators sidesteps the issue while keeping _ITER_NEXT_INLINE for C-level iterators (dict, set, enumerate, zip, reversed, etc.), which the existing test_for_iter_direct_* tests exercise.

@savannahostrowskisavannahostrowski left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yep, this was pretty much the exact fix I had locally. Thanks for the quick turnaround on this @NekoAsakura ❤️

I'll wait to see if anyone else wants to have a look.

@markshannonmarkshannon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good. Thanks for doing this.

@savannahostrowski
savannahostrowski enabled auto-merge (squash) May 7, 2026 16:17
@StanFromIreland

Copy link
Copy Markdown
Member

Updating to fix some errors we introduced on the main branch.

@savannahostrowski
savannahostrowski enabled auto-merge (squash) May 7, 2026 22:34
@savannahostrowski
savannahostrowski merged commit 49918f5 into python:mainMay 7, 2026
70 checks passed
@NekoAsakura
NekoAsakura deleted the gh-149481/skip-slot-iternext branch May 7, 2026 23:40
@NekoAsakura
NekoAsakura restored the gh-149481/skip-slot-iternext branch May 8, 2026 00:08
@Fidget-SpinnerFidget-Spinner added the needs backport to 3.15 pre-release feature fixes, bugs and security fixes label May 8, 2026
@miss-islington-app

Copy link
Copy Markdown

Thanks @NekoAsakura for the PR, and @savannahostrowski for merging it 🌮🎉.. I'm working now to backport this PR to: 3.15.
🐍🍒⛏🤖 I'm not a witch! I'm not a witch!

@bedevere-app

Copy link
Copy Markdown

GH-149523 is a backport of this pull request to the 3.15 branch.

@bedevere-appbedevere-appBot removed the needs backport to 3.15 pre-release feature fixes, bugs and security fixes label May 8, 2026
@NekoAsakura
NekoAsakura deleted the gh-149481/skip-slot-iternext branch May 8, 2026 00:10
savannahostrowski added a commit that referenced this pull request May 8, 2026
…_next__` (GH-149491) (#149523)
gh-149481: skip `FOR_ITER` inline specialization for Python `__next__` (GH-149491)
(cherry picked from commit 49918f5)
Co-authored-by: Neko Asakura <neko.asakura@outlook.com>
Co-authored-by: Savannah Ostrowski <savannah@python.org>
Co-authored-by: Stan Ulbrych <stan@python.org>
@bedevere-bot

Copy link
Copy Markdown

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

Hi! The buildbot aarch64 Android 3.15 (tier-3) has failed when building commit 5cb915d.

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/2113/builds/2) and take a look at the build logs.
  4. Check if the failure is related to this commit (5cb915d) 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/2113/builds/2

Failed tests:

  • test_urllib2
  • test_urllibnet

Failed subtests:

  • test_getcode - test.test_urllibnet.urlopenNetworkTests.test_getcode
  • test_ftp_error - test.test_urllib2.HandlerTests.test_ftp_error

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

==

Click to see traceback logs
Traceback (most recent call last):
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/test/test_urllibnet.py", line 107, in test_getcodeself.assertEqual(e.exception.code, 404)
^^^^^^^^^^^^^^^^AttributeError: 'URLError' object has no attribute 'code'
----------------------------------------------------------------------
Ran 12 tests in 0.018s
FAILED (errors=1, skipped=10)
Traceback (most recent call last):
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/urllib/request.py", line 1531, in ftp_open
host = socket.gethostbyname(host)
socket.gaierror: [Errno 7] No address associated with hostname
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/test/test_urllib2.py", line 815, in test_ftp_error
urlopen("ftp://www.pythontest.net/")
~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/urllib/request.py", line 489, in open
response =self._open(req, data)
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/urllib/request.py", line 506, in _open
result =self._call_chain(self.handle_open, protocol, protocol +'_open', req)
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/urllib/request.py", line 466, in _call_chain
result = func(*args)
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/urllib/request.py", line 1533, in ftp_openraise URLError(msg)
urllib.error.URLError: <urlopen error [Errno 7] No address associated with hostname>
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/test/test_urllib2.py", line 817, in test_ftp_errorself.assertEqual(raised.reason,
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^f"ftp error: {exception.args[0]}")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^AssertionError: gaierror(7, 'No address associated with hostname') != 'ftp error: 500 OOPS: cannot change directory:/nonexistent'
----------------------------------------------------------------------
Ran 81 tests in 0.043s
FAILED (failures=1, skipped=3)
Traceback (most recent call last):
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/test/test_urllibnet.py", line 107, in test_getcodeself.assertEqual(e.exception.code, 404)
^^^^^^^^^^^^^^^^AttributeError: 'URLError' object has no attribute 'code'
----------------------------------------------------------------------
Ran 12 tests in 0.024s
FAILED (errors=1, skipped=10)
Traceback (most recent call last):
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/urllib/request.py", line 1531, in ftp_open
host = socket.gethostbyname(host)
socket.gaierror: [Errno 7] No address associated with hostname
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/test/test_urllib2.py", line 815, in test_ftp_error
urlopen("ftp://www.pythontest.net/")
~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/urllib/request.py", line 489, in open
response =self._open(req, data)
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/urllib/request.py", line 506, in _open
result =self._call_chain(self.handle_open, protocol, protocol +'_open', req)
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/urllib/request.py", line 466, in _call_chain
result = func(*args)
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/urllib/request.py", line 1533, in ftp_openraise URLError(msg)
urllib.error.URLError: <urlopen error [Errno 7] No address associated with hostname>
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/data/user/0/org.python.testbed/files/python/lib/python3.15/test/test_urllib2.py", line 817, in test_ftp_errorself.assertEqual(raised.reason,
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^f"ftp error: {exception.args[0]}")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^AssertionError: gaierror(7, 'No address associated with hostname') != 'ftp error: 500 OOPS: cannot change directory:/nonexistent'
----------------------------------------------------------------------
Ran 81 tests in 0.081s
FAILED (failures=1, skipped=3)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@NekoAsakura@StanFromIreland@bedevere-bot@savannahostrowski@markshannon@Fidget-Spinner