Skip to content

GH-145668: Add FOR_ITER specialization for virtual iterators. Specialize GET_ITER.#147967

Open
markshannon wants to merge 18 commits intopython:mainfrom
markshannon:specialize-iteration-with-jit
Open

GH-145668: Add FOR_ITER specialization for virtual iterators. Specialize GET_ITER.#147967
markshannon wants to merge 18 commits intopython:mainfrom
markshannon:specialize-iteration-with-jit

Conversation

@markshannon
Copy link
Copy Markdown
Member

@markshannon markshannon commented Apr 1, 2026

Add FOR_ITER specialization for virtual iterators. Specialize GET_ITER.

  • Add FOR_ITER_VIRTUAL to specialize FOR_ITER for virtual iterators
  • Add GET_ITER_SELF to specialize GET_ITER for iterators (including generators)
  • Add GET_ITER_VIRTUAL to specialize GET_ITER for iterables as virtual iterators
  • Add tp_iteritem internal slot to PyTypeObject to support a wider range of classes as virtual iterators.

This PR adds strs as virtual iterators, with the potential to add bytes, bytearray, frozenset, frozendict and others in the future.


📚 Documentation preview 📚: https://cpython-previews--147967.org.readthedocs.build/

* Add FOR_ITER_VIRTUAL to specialize FOR_ITER for virtual iterators
* Add GET_ITER_SELF to specialize GET_ITER for iterators (including generators)
* Add GET_ITER_VIRTUAL to specialize GET_ITER for iterables as virtual iterators
@Fidget-Spinner
Copy link
Copy Markdown
Member

Doesn't this change the ABI guarantees of PyTypeObject? Or are there none of those? In any case, feels strange that we are adding a public field that is opaque for our own use.

@markshannon
Copy link
Copy Markdown
Member Author

tp_watchers is already documented as internal.
The header is public, so the fields have to go in a public header even if they aren't to be used.

@markshannon markshannon requested a review from iritkatriel as a code owner April 8, 2026 16:43
Copy link
Copy Markdown
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.

A few comments

+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
| :c:member:`~PyTypeObject.tp_vectorcall` | :c:type:`vectorcallfunc` | | | | | |
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
| [:c:member:`~PyTypeObject.tp_iteritem`] | opaque function pointer | | | | | |
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.

Docs are warning that

c:member reference target not found: PyTypeObject.tp_iteritem [ref.member]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'm not sure what I can do about that. It is defined, but Sphinx won't acknowledge it, unless I define iteritemfunc which I don't want to define, as it is internal.

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.

I think you can remove the :c:memberand just make it a normalcode`` role then?

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.

Name it _tp_iteritemfunc with an underscore, and don't document it.

_GUARD_ITER_VIRTUAL +
_PUSH_TAGGED_ZERO;

op(_GET_ITER_TRAD, (iterable -- iter, index_or_null)) {
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.

what does trad mean?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Traditional

@Fidget-Spinner
Copy link
Copy Markdown
Member

Do you have benchmark numbers for this?

I'm wondering of 3 extra opcodes is worth it. I think I might have a solution for the opcode problem.

@markshannon
Copy link
Copy Markdown
Member Author

Do you have benchmark numbers for this?

In the noise. But this enables more optimizations, so neutral is good.

I'm wondering of 3 extra opcodes is worth it.

The FOR_ITER_VIRTUAL is worth it, as it allows more common iterables to be treated as virtual iterators.

By themselves, the two new GET_ITER specializations probably wouldn't be.
They're worth it because they complement the FOR_ITER and, very soon, the SEND specializations.
For those specializations to work we need GET_ITER to handle the three distinct cases:

  • Already an iterator: do nothing
  • An iterable, supporting virtual iteration: push tagged zero
  • Other iterables, call __iter__ to get the iterator

So the specializations follow naturally.

I think I might have a solution for the opcode problem.

That's good, but not directly relevant to this PR I think.
We aren't in imminent danger of running out of opcodes, provided we followed a reasonably principled approach to specializations.

Include/object.h Outdated
PyObject *object;
Py_ssize_t index;
} _PyObjectIndexPair;
typedef _PyObjectIndexPair (*iteritemfunc) (PyObject *, Py_ssize_t index);
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.

Please name this _Py_iteritemfunc so I don't need to rename it later. If anyone complains, point at me.

+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
| :c:member:`~PyTypeObject.tp_vectorcall` | :c:type:`vectorcallfunc` | | | | | |
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
| [:c:member:`~PyTypeObject.tp_iteritem`] | opaque function pointer | | | | | |
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.

Name it _tp_iteritemfunc with an underscore, and don't document it.

typedef PyObject *(*richcmpfunc) (PyObject *, PyObject *, int);
typedef PyObject *(*getiterfunc) (PyObject *);
typedef PyObject *(*iternextfunc) (PyObject *);
typedef struct _py_object_index_pair {
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.

AFAIK the tag is unused; add it later if that changes.

Suggested change
typedef struct _py_object_index_pair {
typedef struct {

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants