GH-132554: "Virtual" iterators (GH-132555)

* FOR_ITER now pushes either the iterator and NULL or leaves the iterable and pushes tagged zero

* NEXT_ITER uses the tagged int as the index into the sequence or, if TOS is NULL, iterates as before.
This commit is contained in:
Mark Shannon 2025-05-27 15:59:45 +01:00 committed by GitHub
parent 9300a596d3
commit f6f4e8a662
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 713 additions and 618 deletions

View file

@ -353,6 +353,8 @@ PyAPI_FUNC(_PyStackRef) _PyFloat_FromDouble_ConsumeInputs(_PyStackRef left, _PyS
extern int _PyRunRemoteDebugger(PyThreadState *tstate);
#endif
_PyStackRef _PyForIter_NextWithIndex(PyObject *seq, _PyStackRef index);
#ifdef __cplusplus
}
#endif

View file

@ -313,7 +313,7 @@ extern void _Py_Specialize_CompareOp(_PyStackRef lhs, _PyStackRef rhs,
_Py_CODEUNIT *instr, int oparg);
extern void _Py_Specialize_UnpackSequence(_PyStackRef seq, _Py_CODEUNIT *instr,
int oparg);
extern void _Py_Specialize_ForIter(_PyStackRef iter, _Py_CODEUNIT *instr, int oparg);
extern void _Py_Specialize_ForIter(_PyStackRef iter, _PyStackRef null_or_index, _Py_CODEUNIT *instr, int oparg);
extern void _Py_Specialize_Send(_PyStackRef receiver, _Py_CODEUNIT *instr);
extern void _Py_Specialize_ToBool(_PyStackRef value, _Py_CODEUNIT *instr);
extern void _Py_Specialize_ContainsOp(_PyStackRef value, _Py_CODEUNIT *instr);

View file

@ -95,6 +95,7 @@ typedef enum {
enum _PyCompile_FBlockType {
COMPILE_FBLOCK_WHILE_LOOP,
COMPILE_FBLOCK_FOR_LOOP,
COMPILE_FBLOCK_ASYNC_FOR_LOOP,
COMPILE_FBLOCK_TRY_EXCEPT,
COMPILE_FBLOCK_FINALLY_TRY,
COMPILE_FBLOCK_FINALLY_END,

View file

@ -279,6 +279,8 @@ Known values:
Python 3.14b1 3624 (Don't optimize LOAD_FAST when local is killed by DELETE_FAST)
Python 3.15a0 3650 (Initial version)
Python 3.15a1 3651 (Simplify LOAD_CONST)
Python 3.15a1 3652 (Virtual iterators)
Python 3.16 will start with 3700
@ -291,7 +293,7 @@ PC/launcher.c must also be updated.
*/
#define PYC_MAGIC_NUMBER 3651
#define PYC_MAGIC_NUMBER 3652
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
(little-endian) and then appending b'\r\n'. */
#define PYC_MAGIC_NUMBER_TOKEN \

View file

@ -205,15 +205,15 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
case FORMAT_WITH_SPEC:
return 2;
case FOR_ITER:
return 1;
return 2;
case FOR_ITER_GEN:
return 1;
return 2;
case FOR_ITER_LIST:
return 1;
return 2;
case FOR_ITER_RANGE:
return 1;
return 2;
case FOR_ITER_TUPLE:
return 1;
return 2;
case GET_AITER:
return 1;
case GET_ANEXT:
@ -239,11 +239,11 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
case INSTRUMENTED_END_ASYNC_FOR:
return 2;
case INSTRUMENTED_END_FOR:
return 2;
return 3;
case INSTRUMENTED_END_SEND:
return 2;
case INSTRUMENTED_FOR_ITER:
return 1;
return 2;
case INSTRUMENTED_INSTRUCTION:
return 0;
case INSTRUMENTED_JUMP_BACKWARD:
@ -257,7 +257,7 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
case INSTRUMENTED_NOT_TAKEN:
return 0;
case INSTRUMENTED_POP_ITER:
return 1;
return 2;
case INSTRUMENTED_POP_JUMP_IF_FALSE:
return 1;
case INSTRUMENTED_POP_JUMP_IF_NONE:
@ -395,7 +395,7 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
case POP_EXCEPT:
return 1;
case POP_ITER:
return 1;
return 2;
case POP_JUMP_IF_FALSE:
return 1;
case POP_JUMP_IF_NONE:
@ -688,15 +688,15 @@ int _PyOpcode_num_pushed(int opcode, int oparg) {
case FORMAT_WITH_SPEC:
return 1;
case FOR_ITER:
return 2;
return 3;
case FOR_ITER_GEN:
return 1;
return 2;
case FOR_ITER_LIST:
return 2;
return 3;
case FOR_ITER_RANGE:
return 2;
return 3;
case FOR_ITER_TUPLE:
return 2;
return 3;
case GET_AITER:
return 1;
case GET_ANEXT:
@ -704,7 +704,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg) {
case GET_AWAITABLE:
return 1;
case GET_ITER:
return 1;
return 2;
case GET_LEN:
return 2;
case GET_YIELD_FROM_ITER:
@ -722,11 +722,11 @@ int _PyOpcode_num_pushed(int opcode, int oparg) {
case INSTRUMENTED_END_ASYNC_FOR:
return 0;
case INSTRUMENTED_END_FOR:
return 1;
return 2;
case INSTRUMENTED_END_SEND:
return 1;
case INSTRUMENTED_FOR_ITER:
return 2;
return 3;
case INSTRUMENTED_INSTRUCTION:
return 0;
case INSTRUMENTED_JUMP_BACKWARD:
@ -1157,7 +1157,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
[FOR_ITER_GEN] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG },
[FOR_ITER_LIST] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[FOR_ITER_RANGE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG },
[FOR_ITER_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[FOR_ITER_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EXIT_FLAG },
[GET_AITER] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[GET_ANEXT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
[GET_AWAITABLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
@ -1242,7 +1242,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
[NOP] = { true, INSTR_FMT_IX, HAS_PURE_FLAG },
[NOT_TAKEN] = { true, INSTR_FMT_IX, HAS_PURE_FLAG },
[POP_EXCEPT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG },
[POP_ITER] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG | HAS_PURE_FLAG },
[POP_ITER] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG },
[POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG },
[POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ESCAPES_FLAG },
[POP_JUMP_IF_NOT_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ESCAPES_FLAG },
@ -1453,7 +1453,7 @@ _PyOpcode_macro_expansion[256] = {
[NOP] = { .nuops = 1, .uops = { { _NOP, OPARG_SIMPLE, 0 } } },
[NOT_TAKEN] = { .nuops = 1, .uops = { { _NOP, OPARG_SIMPLE, 0 } } },
[POP_EXCEPT] = { .nuops = 1, .uops = { { _POP_EXCEPT, OPARG_SIMPLE, 0 } } },
[POP_ITER] = { .nuops = 1, .uops = { { _POP_TOP, OPARG_SIMPLE, 0 } } },
[POP_ITER] = { .nuops = 1, .uops = { { _POP_ITER, OPARG_SIMPLE, 0 } } },
[POP_JUMP_IF_FALSE] = { .nuops = 1, .uops = { { _POP_JUMP_IF_FALSE, OPARG_REPLACED, 1 } } },
[POP_JUMP_IF_NONE] = { .nuops = 2, .uops = { { _IS_NONE, OPARG_SIMPLE, 1 }, { _POP_JUMP_IF_TRUE, OPARG_REPLACED, 1 } } },
[POP_JUMP_IF_NOT_NONE] = { .nuops = 2, .uops = { { _IS_NONE, OPARG_SIMPLE, 1 }, { _POP_JUMP_IF_FALSE, OPARG_REPLACED, 1 } } },

View file

@ -232,6 +232,9 @@ extern intptr_t PyStackRef_UntagInt(_PyStackRef ref);
extern _PyStackRef PyStackRef_TagInt(intptr_t i);
/* Increments a tagged int, but does not check for overflow */
extern _PyStackRef PyStackRef_IncrementTaggedIntNoOverflow(_PyStackRef ref);
extern bool
PyStackRef_IsNullOrInt(_PyStackRef ref);
@ -239,11 +242,12 @@ PyStackRef_IsNullOrInt(_PyStackRef ref);
#define Py_INT_TAG 3
#define Py_TAG_REFCNT 1
#define Py_TAG_BITS 3
static inline bool
PyStackRef_IsTaggedInt(_PyStackRef i)
{
return (i.bits & Py_INT_TAG) == Py_INT_TAG;
return (i.bits & Py_TAG_BITS) == Py_INT_TAG;
}
static inline _PyStackRef
@ -262,12 +266,21 @@ PyStackRef_UntagInt(_PyStackRef i)
}
static inline _PyStackRef
PyStackRef_IncrementTaggedIntNoOverflow(_PyStackRef ref)
{
assert((ref.bits & Py_TAG_BITS) == Py_INT_TAG); // Is tagged int
assert((ref.bits & (~Py_TAG_BITS)) != (INT_MAX & (~Py_TAG_BITS))); // Isn't about to overflow
return (_PyStackRef){ .bits = ref.bits + 4 };
}
#define PyStackRef_IsDeferredOrTaggedInt(ref) (((ref).bits & Py_TAG_REFCNT) != 0)
#ifdef Py_GIL_DISABLED
#define Py_TAG_DEFERRED Py_TAG_REFCNT
#define Py_TAG_PTR ((uintptr_t)0)
#define Py_TAG_BITS ((uintptr_t)1)
static const _PyStackRef PyStackRef_NULL = { .bits = Py_TAG_DEFERRED};
@ -379,7 +392,7 @@ PyStackRef_FromPyObjectBorrow(PyObject *obj)
do { \
_PyStackRef _close_tmp = (REF); \
assert(!PyStackRef_IsNull(_close_tmp)); \
if (!PyStackRef_IsDeferred(_close_tmp)) { \
if (!PyStackRef_IsDeferredOrTaggedInt(_close_tmp)) { \
Py_DECREF(PyStackRef_AsPyObjectBorrow(_close_tmp)); \
} \
} while (0)
@ -395,7 +408,7 @@ static inline _PyStackRef
PyStackRef_DUP(_PyStackRef stackref)
{
assert(!PyStackRef_IsNull(stackref));
if (PyStackRef_IsDeferred(stackref)) {
if (PyStackRef_IsDeferredOrTaggedInt(stackref)) {
return stackref;
}
Py_INCREF(PyStackRef_AsPyObjectBorrow(stackref));
@ -442,7 +455,6 @@ PyStackRef_AsStrongReference(_PyStackRef stackref)
/* References to immortal objects always have their tag bit set to Py_TAG_REFCNT
* as they can (must) have their reclamation deferred */
#define Py_TAG_BITS 3
#if _Py_IMMORTAL_FLAGS != Py_TAG_REFCNT
# error "_Py_IMMORTAL_FLAGS != Py_TAG_REFCNT"
#endif
@ -678,7 +690,13 @@ PyStackRef_XCLOSE(_PyStackRef ref)
#endif // !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
#define PyStackRef_TYPE(stackref) Py_TYPE(PyStackRef_AsPyObjectBorrow(stackref))
static inline PyTypeObject *
PyStackRef_TYPE(_PyStackRef stackref) {
if (PyStackRef_IsTaggedInt(stackref)) {
return &PyLong_Type;
}
return Py_TYPE(PyStackRef_AsPyObjectBorrow(stackref));
}
// Converts a PyStackRef back to a PyObject *, converting the
// stackref to a new reference.
@ -686,42 +704,30 @@ PyStackRef_XCLOSE(_PyStackRef ref)
// StackRef type checks
static inline bool
PyStackRef_GenCheck(_PyStackRef stackref)
{
return PyGen_Check(PyStackRef_AsPyObjectBorrow(stackref));
}
#define STACKREF_CHECK_FUNC(T) \
static inline bool \
PyStackRef_ ## T ## Check(_PyStackRef stackref) { \
if (PyStackRef_IsTaggedInt(stackref)) { \
return false; \
} \
return Py ## T ## _Check(PyStackRef_AsPyObjectBorrow(stackref)); \
}
static inline bool
PyStackRef_BoolCheck(_PyStackRef stackref)
{
return PyBool_Check(PyStackRef_AsPyObjectBorrow(stackref));
}
STACKREF_CHECK_FUNC(Gen)
STACKREF_CHECK_FUNC(Bool)
STACKREF_CHECK_FUNC(ExceptionInstance)
STACKREF_CHECK_FUNC(Code)
STACKREF_CHECK_FUNC(Function)
static inline bool
PyStackRef_LongCheck(_PyStackRef stackref)
{
if (PyStackRef_IsTaggedInt(stackref)) {
return true;
}
return PyLong_Check(PyStackRef_AsPyObjectBorrow(stackref));
}
static inline bool
PyStackRef_ExceptionInstanceCheck(_PyStackRef stackref)
{
return PyExceptionInstance_Check(PyStackRef_AsPyObjectBorrow(stackref));
}
static inline bool
PyStackRef_CodeCheck(_PyStackRef stackref)
{
return PyCode_Check(PyStackRef_AsPyObjectBorrow(stackref));
}
static inline bool
PyStackRef_FunctionCheck(_PyStackRef stackref)
{
return PyFunction_Check(PyStackRef_AsPyObjectBorrow(stackref));
}
static inline void
_PyThreadState_PushCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
{

View file

@ -272,6 +272,7 @@ extern "C" {
#define _POP_CALL_TWO 489
#define _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW 490
#define _POP_EXCEPT POP_EXCEPT
#define _POP_ITER POP_ITER
#define _POP_JUMP_IF_FALSE 491
#define _POP_JUMP_IF_TRUE 492
#define _POP_TOP POP_TOP

View file

@ -66,6 +66,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_POP_TWO] = HAS_ESCAPES_FLAG,
[_PUSH_NULL] = HAS_PURE_FLAG,
[_END_FOR] = HAS_ESCAPES_FLAG | HAS_NO_SAVE_IP_FLAG,
[_POP_ITER] = HAS_ESCAPES_FLAG,
[_END_SEND] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG,
[_UNARY_NEGATIVE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_UNARY_NOT] = HAS_PURE_FLAG,
@ -205,7 +206,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_FOR_ITER_TIER_TWO] = HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG,
[_ITER_CHECK_LIST] = HAS_EXIT_FLAG,
[_GUARD_NOT_EXHAUSTED_LIST] = HAS_EXIT_FLAG,
[_ITER_NEXT_LIST_TIER_TWO] = HAS_EXIT_FLAG | HAS_ESCAPES_FLAG,
[_ITER_NEXT_LIST_TIER_TWO] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_ITER_CHECK_TUPLE] = HAS_EXIT_FLAG,
[_GUARD_NOT_EXHAUSTED_TUPLE] = HAS_EXIT_FLAG,
[_ITER_NEXT_TUPLE] = 0,
@ -569,6 +570,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {
[_POP_CALL_TWO] = "_POP_CALL_TWO",
[_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = "_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW",
[_POP_EXCEPT] = "_POP_EXCEPT",
[_POP_ITER] = "_POP_ITER",
[_POP_TOP] = "_POP_TOP",
[_POP_TOP_LOAD_CONST_INLINE] = "_POP_TOP_LOAD_CONST_INLINE",
[_POP_TOP_LOAD_CONST_INLINE_BORROW] = "_POP_TOP_LOAD_CONST_INLINE_BORROW",
@ -730,6 +732,8 @@ int _PyUop_num_popped(int opcode, int oparg)
return 0;
case _END_FOR:
return 1;
case _POP_ITER:
return 2;
case _END_SEND:
return 2;
case _UNARY_NEGATIVE: