Skip to content

Commit f6f4e8a

Browse files
authored
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.
1 parent 9300a59 commit f6f4e8a

25 files changed

+709
-614
lines changed

Include/internal/pycore_ceval.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,8 @@ PyAPI_FUNC(_PyStackRef) _PyFloat_FromDouble_ConsumeInputs(_PyStackRef left, _PyS
353353
extern int _PyRunRemoteDebugger(PyThreadState *tstate);
354354
#endif
355355

356+
_PyStackRef _PyForIter_NextWithIndex(PyObject *seq, _PyStackRef index);
357+
356358
#ifdef __cplusplus
357359
}
358360
#endif

Include/internal/pycore_code.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ extern void _Py_Specialize_CompareOp(_PyStackRef lhs, _PyStackRef rhs,
313313
_Py_CODEUNIT *instr, int oparg);
314314
extern void _Py_Specialize_UnpackSequence(_PyStackRef seq, _Py_CODEUNIT *instr,
315315
int oparg);
316-
extern void _Py_Specialize_ForIter(_PyStackRef iter, _Py_CODEUNIT *instr, int oparg);
316+
extern void _Py_Specialize_ForIter(_PyStackRef iter, _PyStackRef null_or_index, _Py_CODEUNIT *instr, int oparg);
317317
extern void _Py_Specialize_Send(_PyStackRef receiver, _Py_CODEUNIT *instr);
318318
extern void _Py_Specialize_ToBool(_PyStackRef value, _Py_CODEUNIT *instr);
319319
extern void _Py_Specialize_ContainsOp(_PyStackRef value, _Py_CODEUNIT *instr);

Include/internal/pycore_compile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ typedef enum {
9595
enum _PyCompile_FBlockType {
9696
COMPILE_FBLOCK_WHILE_LOOP,
9797
COMPILE_FBLOCK_FOR_LOOP,
98+
COMPILE_FBLOCK_ASYNC_FOR_LOOP,
9899
COMPILE_FBLOCK_TRY_EXCEPT,
99100
COMPILE_FBLOCK_FINALLY_TRY,
100101
COMPILE_FBLOCK_FINALLY_END,

Include/internal/pycore_magic_number.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ Known values:
279279
Python 3.14b1 3624 (Don't optimize LOAD_FAST when local is killed by DELETE_FAST)
280280
Python 3.15a0 3650 (Initial version)
281281
Python 3.15a1 3651 (Simplify LOAD_CONST)
282+
Python 3.15a1 3652 (Virtual iterators)
283+
282284
283285
Python 3.16 will start with 3700
284286
@@ -291,7 +293,7 @@ PC/launcher.c must also be updated.
291293
292294
*/
293295

294-
#define PYC_MAGIC_NUMBER 3651
296+
#define PYC_MAGIC_NUMBER 3652
295297
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
296298
(little-endian) and then appending b'\r\n'. */
297299
#define PYC_MAGIC_NUMBER_TOKEN \

Include/internal/pycore_opcode_metadata.h

Lines changed: 20 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_stackref.h

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -232,18 +232,22 @@ extern intptr_t PyStackRef_UntagInt(_PyStackRef ref);
232232

233233
extern _PyStackRef PyStackRef_TagInt(intptr_t i);
234234

235+
/* Increments a tagged int, but does not check for overflow */
236+
extern _PyStackRef PyStackRef_IncrementTaggedIntNoOverflow(_PyStackRef ref);
237+
235238
extern bool
236239
PyStackRef_IsNullOrInt(_PyStackRef ref);
237240

238241
#else
239242

240243
#define Py_INT_TAG 3
241244
#define Py_TAG_REFCNT 1
245+
#define Py_TAG_BITS 3
242246

243247
static inline bool
244248
PyStackRef_IsTaggedInt(_PyStackRef i)
245249
{
246-
return (i.bits & Py_INT_TAG) == Py_INT_TAG;
250+
return (i.bits & Py_TAG_BITS) == Py_INT_TAG;
247251
}
248252

249253
static inline _PyStackRef
@@ -262,12 +266,21 @@ PyStackRef_UntagInt(_PyStackRef i)
262266
}
263267

264268

269+
static inline _PyStackRef
270+
PyStackRef_IncrementTaggedIntNoOverflow(_PyStackRef ref)
271+
{
272+
assert((ref.bits & Py_TAG_BITS) == Py_INT_TAG); // Is tagged int
273+
assert((ref.bits & (~Py_TAG_BITS)) != (INT_MAX & (~Py_TAG_BITS))); // Isn't about to overflow
274+
return (_PyStackRef){ .bits = ref.bits + 4 };
275+
}
276+
277+
#define PyStackRef_IsDeferredOrTaggedInt(ref) (((ref).bits & Py_TAG_REFCNT) != 0)
278+
265279
#ifdef Py_GIL_DISABLED
266280

267281
#define Py_TAG_DEFERRED Py_TAG_REFCNT
268282

269283
#define Py_TAG_PTR ((uintptr_t)0)
270-
#define Py_TAG_BITS ((uintptr_t)1)
271284

272285

273286
static const _PyStackRef PyStackRef_NULL = { .bits = Py_TAG_DEFERRED};
@@ -379,7 +392,7 @@ PyStackRef_FromPyObjectBorrow(PyObject *obj)
379392
do { \
380393
_PyStackRef _close_tmp = (REF); \
381394
assert(!PyStackRef_IsNull(_close_tmp)); \
382-
if (!PyStackRef_IsDeferred(_close_tmp)) { \
395+
if (!PyStackRef_IsDeferredOrTaggedInt(_close_tmp)) { \
383396
Py_DECREF(PyStackRef_AsPyObjectBorrow(_close_tmp)); \
384397
} \
385398
} while (0)
@@ -395,7 +408,7 @@ static inline _PyStackRef
395408
PyStackRef_DUP(_PyStackRef stackref)
396409
{
397410
assert(!PyStackRef_IsNull(stackref));
398-
if (PyStackRef_IsDeferred(stackref)) {
411+
if (PyStackRef_IsDeferredOrTaggedInt(stackref)) {
399412
return stackref;
400413
}
401414
Py_INCREF(PyStackRef_AsPyObjectBorrow(stackref));
@@ -442,7 +455,6 @@ PyStackRef_AsStrongReference(_PyStackRef stackref)
442455
/* References to immortal objects always have their tag bit set to Py_TAG_REFCNT
443456
* as they can (must) have their reclamation deferred */
444457

445-
#define Py_TAG_BITS 3
446458
#if _Py_IMMORTAL_FLAGS != Py_TAG_REFCNT
447459
# error "_Py_IMMORTAL_FLAGS != Py_TAG_REFCNT"
448460
#endif
@@ -678,50 +690,44 @@ PyStackRef_XCLOSE(_PyStackRef ref)
678690

679691
#endif // !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
680692

681-
#define PyStackRef_TYPE(stackref) Py_TYPE(PyStackRef_AsPyObjectBorrow(stackref))
693+
static inline PyTypeObject *
694+
PyStackRef_TYPE(_PyStackRef stackref) {
695+
if (PyStackRef_IsTaggedInt(stackref)) {
696+
return &PyLong_Type;
697+
}
698+
return Py_TYPE(PyStackRef_AsPyObjectBorrow(stackref));
699+
}
682700

683701
// Converts a PyStackRef back to a PyObject *, converting the
684702
// stackref to a new reference.
685703
#define PyStackRef_AsPyObjectNew(stackref) Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref))
686704

687705
// StackRef type checks
688706

689-
static inline bool
690-
PyStackRef_GenCheck(_PyStackRef stackref)
691-
{
692-
return PyGen_Check(PyStackRef_AsPyObjectBorrow(stackref));
693-
}
707+
#define STACKREF_CHECK_FUNC(T) \
708+
static inline bool \
709+
PyStackRef_ ## T ## Check(_PyStackRef stackref) { \
710+
if (PyStackRef_IsTaggedInt(stackref)) { \
711+
return false; \
712+
} \
713+
return Py ## T ## _Check(PyStackRef_AsPyObjectBorrow(stackref)); \
714+
}
694715

695-
static inline bool
696-
PyStackRef_BoolCheck(_PyStackRef stackref)
697-
{
698-
return PyBool_Check(PyStackRef_AsPyObjectBorrow(stackref));
699-
}
716+
STACKREF_CHECK_FUNC(Gen)
717+
STACKREF_CHECK_FUNC(Bool)
718+
STACKREF_CHECK_FUNC(ExceptionInstance)
719+
STACKREF_CHECK_FUNC(Code)
720+
STACKREF_CHECK_FUNC(Function)
700721

701722
static inline bool
702723
PyStackRef_LongCheck(_PyStackRef stackref)
703724
{
725+
if (PyStackRef_IsTaggedInt(stackref)) {
726+
return true;
727+
}
704728
return PyLong_Check(PyStackRef_AsPyObjectBorrow(stackref));
705729
}
706730

707-
static inline bool
708-
PyStackRef_ExceptionInstanceCheck(_PyStackRef stackref)
709-
{
710-
return PyExceptionInstance_Check(PyStackRef_AsPyObjectBorrow(stackref));
711-
}
712-
713-
static inline bool
714-
PyStackRef_CodeCheck(_PyStackRef stackref)
715-
{
716-
return PyCode_Check(PyStackRef_AsPyObjectBorrow(stackref));
717-
}
718-
719-
static inline bool
720-
PyStackRef_FunctionCheck(_PyStackRef stackref)
721-
{
722-
return PyFunction_Check(PyStackRef_AsPyObjectBorrow(stackref));
723-
}
724-
725731
static inline void
726732
_PyThreadState_PushCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
727733
{

Include/internal/pycore_uop_ids.h

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)