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

* 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 new (internal) _tp_iteritem function slot to PyTypeObject
* Put limited RESUME at start of genexpr for free-threading. Fix up exception handling in genexpr
This commit is contained in:
Mark Shannon 2026-04-16 15:22:22 +01:00 committed by GitHub
parent 0fcf2b72d3
commit 600f4dbd54
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 2987 additions and 1534 deletions

View file

@ -199,7 +199,7 @@ dummy_func(
}
}
op(_LOAD_BYTECODE, (--)) {
replaced op(_LOAD_BYTECODE, (--)) {
#ifdef Py_GIL_DISABLED
if (frame->tlbc_index !=
((_PyThreadStateImpl *)tstate)->tlbc_index) {
@ -2820,6 +2820,11 @@ dummy_func(
}
}
op(_GUARD_TYPE, (type/4, owner -- owner)) {
PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner));
EXIT_IF(tp != (PyTypeObject *)type);
}
op(_CHECK_MANAGED_OBJECT_HAS_VALUES, (owner -- owner)) {
PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner);
assert(Py_TYPE(owner_o)->tp_dictoffset < 0);
@ -3655,16 +3660,71 @@ dummy_func(
values_or_none = PyStackRef_FromPyObjectSteal(values_or_none_o);
}
inst(GET_ITER, (iterable -- iter, index_or_null)) {
#ifdef Py_STATS
_Py_GatherStats_GetIter(iterable);
#endif
family(GET_ITER, INLINE_CACHE_ENTRIES_GET_ITER) = {
GET_ITER_SELF,
GET_ITER_VIRTUAL,
};
specializing op(_SPECIALIZE_GET_ITER, (counter/1, iterable -- iterable)) {
#if ENABLE_SPECIALIZATION
if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
next_instr = this_instr;
_Py_Specialize_GetIter(iterable, next_instr);
DISPATCH_SAME_OPARG();
}
OPCODE_DEFERRED_INC(GET_ITER);
ADVANCE_ADAPTIVE_COUNTER(this_instr[1].counter);
#endif /* ENABLE_SPECIALIZATION */
}
op(_GET_ITER, (iterable -- iter, index_or_null)) {
_PyStackRef result = _PyEval_GetIter(iterable, &index_or_null, oparg);
DEAD(iterable);
ERROR_IF(PyStackRef_IsError(result));
iter = result;
}
macro(GET_ITER) =
_RECORD_TOS_TYPE +
_SPECIALIZE_GET_ITER +
_GET_ITER;
op(_GUARD_ITERATOR, (iterable -- iterable)) {
PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(iterable));
EXIT_IF(tp->tp_iter != PyObject_SelfIter);
STAT_INC(GET_ITER, hit);
}
macro(GET_ITER_SELF) =
_RECORD_TOS_TYPE +
unused/1 +
_GUARD_ITERATOR +
PUSH_NULL;
op(_GUARD_ITER_VIRTUAL, (iterable -- iterable)) {
PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(iterable));
EXIT_IF(tp->_tp_iteritem == NULL);
STAT_INC(GET_ITER, hit);
}
op(_PUSH_TAGGED_ZERO, ( -- zero)) {
zero = PyStackRef_TagInt(0);
}
macro(GET_ITER_VIRTUAL) =
_RECORD_TOS_TYPE +
unused/1 +
_GUARD_ITER_VIRTUAL +
_PUSH_TAGGED_ZERO;
op(_GET_ITER_TRAD, (iterable -- iter, index_or_null)) {
PyObject *iter_o = PyObject_GetIter(PyStackRef_AsPyObjectBorrow(iterable));
PyStackRef_CLOSE(iterable);
ERROR_IF(iter_o == NULL);
iter = PyStackRef_FromPyObjectSteal(iter_o);
index_or_null = PyStackRef_NULL;
}
// Most members of this family are "secretly" super-instructions.
// When the loop is exhausted, they jump, and the jump target is
// always END_FOR, which pops two values off the stack.
@ -3676,6 +3736,7 @@ dummy_func(
FOR_ITER_TUPLE,
FOR_ITER_RANGE,
FOR_ITER_GEN,
FOR_ITER_VIRTUAL,
};
specializing op(_SPECIALIZE_FOR_ITER, (counter/1, iter, null_or_index -- iter, null_or_index)) {
@ -3703,6 +3764,8 @@ dummy_func(
next = item;
}
macro(FOR_ITER) = _SPECIALIZE_FOR_ITER + _FOR_ITER;
op(_FOR_ITER_TIER_TWO, (iter, null_or_index -- iter, null_or_index, next)) {
_PyStackRef item = _PyForIter_VirtualIteratorNext(tstate, frame, iter, &null_or_index);
if (!PyStackRef_IsValid(item)) {
@ -3716,9 +3779,51 @@ dummy_func(
next = item;
}
op(_GUARD_NOS_ITER_VIRTUAL, (iter, null_or_index -- iter, null_or_index)) {
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
EXIT_IF(Py_TYPE(iter_o)->_tp_iteritem == NULL);
}
macro(FOR_ITER) = _SPECIALIZE_FOR_ITER + _FOR_ITER;
replaced op(_FOR_ITER_VIRTUAL, (iter, null_or_index -- iter, null_or_index, next)) {
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
Py_ssize_t index = PyStackRef_UntagInt(null_or_index);
_PyObjectIndexPair next_index = Py_TYPE(iter_o)->_tp_iteritem(iter_o, index);
PyObject *next_o = next_index.object;
index = next_index.index;
if (next_o == NULL) {
if (index < 0) {
ERROR_NO_POP();
}
// Jump forward by oparg and skip the following END_FOR
JUMPBY(oparg + 1);
DISPATCH();
}
null_or_index = PyStackRef_TagInt(index);
next = PyStackRef_FromPyObjectSteal(next_o);
}
macro(FOR_ITER_VIRTUAL) =
unused/1 + // Skip over the counter
_GUARD_NOS_ITER_VIRTUAL +
_FOR_ITER_VIRTUAL;
op(_FOR_ITER_VIRTUAL_TIER_TWO, (iter, null_or_index -- iter, null_or_index, next)) {
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
Py_ssize_t index = PyStackRef_UntagInt(null_or_index);
_PyObjectIndexPair next_index = Py_TYPE(iter_o)->_tp_iteritem(iter_o, index);
PyObject *next_o = next_index.object;
index = next_index.index;
if (next_o == NULL) {
if (index < 0) {
ERROR_NO_POP();
}
/* iterator ended normally */
/* The translator sets the deopt target just past the matching END_FOR */
EXIT_IF(true);
}
next = PyStackRef_FromPyObjectSteal(next_o);
null_or_index = PyStackRef_TagInt(index);
}
inst(INSTRUMENTED_FOR_ITER, (unused/1, iter, null_or_index -- iter, null_or_index, next)) {
_PyStackRef item = _PyForIter_VirtualIteratorNext(tstate, frame, iter, &null_or_index);