Add free-threaded versions of existing specialization for FOR_ITER (list, tuples, fast range iterators and generators), without significantly affecting their thread-safety. (Iterating over shared lists/tuples/ranges should be fine like before. Reusing iterators between threads is not fine, like before. Sharing generators between threads is a recipe for significant crashes, like before.)
# error "this header requires Py_BUILD_CORE define"
#endif
+#ifdef Py_GIL_DISABLED
+#include "pycore_stackref.h"
+#endif
+
PyAPI_FUNC(PyObject*) _PyList_Extend(PyListObject *, PyObject *);
extern void _PyList_DebugMallocStats(FILE *out);
// _PyList_GetItemRef should be used only when the object is known as a list
// because it doesn't raise TypeError when the object is not a list, whereas PyList_GetItemRef does.
extern PyObject* _PyList_GetItemRef(PyListObject *, Py_ssize_t i);
+#ifdef Py_GIL_DISABLED
+// Returns -1 in case of races with other threads.
+extern int _PyList_GetItemRefNoLock(PyListObject *, Py_ssize_t, _PyStackRef *);
+#endif
#define _PyList_ITEMS(op) _Py_RVALUE(_PyList_CAST(op)->ob_item)
[FORMAT_WITH_SPEC] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
[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_EXIT_FLAG | HAS_ESCAPES_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 },
[GET_AITER] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[FORMAT_WITH_SPEC] = { .nuops = 1, .uops = { { _FORMAT_WITH_SPEC, OPARG_SIMPLE, 0 } } },
[FOR_ITER] = { .nuops = 1, .uops = { { _FOR_ITER, OPARG_REPLACED, 0 } } },
[FOR_ITER_GEN] = { .nuops = 3, .uops = { { _CHECK_PEP_523, OPARG_SIMPLE, 1 }, { _FOR_ITER_GEN_FRAME, OPARG_SIMPLE, 1 }, { _PUSH_FRAME, OPARG_SIMPLE, 1 } } },
- [FOR_ITER_LIST] = { .nuops = 3, .uops = { { _ITER_CHECK_LIST, OPARG_SIMPLE, 1 }, { _ITER_JUMP_LIST, OPARG_REPLACED, 1 }, { _ITER_NEXT_LIST, OPARG_SIMPLE, 1 } } },
+ [FOR_ITER_LIST] = { .nuops = 3, .uops = { { _ITER_CHECK_LIST, OPARG_SIMPLE, 1 }, { _ITER_JUMP_LIST, OPARG_REPLACED, 1 }, { _ITER_NEXT_LIST, OPARG_REPLACED, 1 } } },
[FOR_ITER_RANGE] = { .nuops = 3, .uops = { { _ITER_CHECK_RANGE, OPARG_SIMPLE, 1 }, { _ITER_JUMP_RANGE, OPARG_REPLACED, 1 }, { _ITER_NEXT_RANGE, OPARG_SIMPLE, 1 } } },
[FOR_ITER_TUPLE] = { .nuops = 3, .uops = { { _ITER_CHECK_TUPLE, OPARG_SIMPLE, 1 }, { _ITER_JUMP_TUPLE, OPARG_REPLACED, 1 }, { _ITER_NEXT_TUPLE, OPARG_SIMPLE, 1 } } },
[GET_AITER] = { .nuops = 1, .uops = { { _GET_AITER, OPARG_SIMPLE, 0 } } },
#define _ITER_JUMP_RANGE 398
#define _ITER_JUMP_TUPLE 399
#define _ITER_NEXT_LIST 400
-#define _ITER_NEXT_RANGE 401
-#define _ITER_NEXT_TUPLE 402
-#define _JUMP_TO_TOP 403
+#define _ITER_NEXT_LIST_TIER_TWO 401
+#define _ITER_NEXT_RANGE 402
+#define _ITER_NEXT_TUPLE 403
+#define _JUMP_TO_TOP 404
#define _LIST_APPEND LIST_APPEND
#define _LIST_EXTEND LIST_EXTEND
-#define _LOAD_ATTR 404
-#define _LOAD_ATTR_CLASS 405
+#define _LOAD_ATTR 405
+#define _LOAD_ATTR_CLASS 406
#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN
-#define _LOAD_ATTR_INSTANCE_VALUE 406
-#define _LOAD_ATTR_METHOD_LAZY_DICT 407
-#define _LOAD_ATTR_METHOD_NO_DICT 408
-#define _LOAD_ATTR_METHOD_WITH_VALUES 409
-#define _LOAD_ATTR_MODULE 410
-#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 411
-#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 412
-#define _LOAD_ATTR_PROPERTY_FRAME 413
-#define _LOAD_ATTR_SLOT 414
-#define _LOAD_ATTR_WITH_HINT 415
+#define _LOAD_ATTR_INSTANCE_VALUE 407
+#define _LOAD_ATTR_METHOD_LAZY_DICT 408
+#define _LOAD_ATTR_METHOD_NO_DICT 409
+#define _LOAD_ATTR_METHOD_WITH_VALUES 410
+#define _LOAD_ATTR_MODULE 411
+#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 412
+#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 413
+#define _LOAD_ATTR_PROPERTY_FRAME 414
+#define _LOAD_ATTR_SLOT 415
+#define _LOAD_ATTR_WITH_HINT 416
#define _LOAD_BUILD_CLASS LOAD_BUILD_CLASS
-#define _LOAD_BYTECODE 416
+#define _LOAD_BYTECODE 417
#define _LOAD_COMMON_CONSTANT LOAD_COMMON_CONSTANT
#define _LOAD_CONST LOAD_CONST
#define _LOAD_CONST_IMMORTAL LOAD_CONST_IMMORTAL
-#define _LOAD_CONST_INLINE 417
-#define _LOAD_CONST_INLINE_BORROW 418
+#define _LOAD_CONST_INLINE 418
+#define _LOAD_CONST_INLINE_BORROW 419
#define _LOAD_CONST_MORTAL LOAD_CONST_MORTAL
#define _LOAD_DEREF LOAD_DEREF
-#define _LOAD_FAST 419
-#define _LOAD_FAST_0 420
-#define _LOAD_FAST_1 421
-#define _LOAD_FAST_2 422
-#define _LOAD_FAST_3 423
-#define _LOAD_FAST_4 424
-#define _LOAD_FAST_5 425
-#define _LOAD_FAST_6 426
-#define _LOAD_FAST_7 427
+#define _LOAD_FAST 420
+#define _LOAD_FAST_0 421
+#define _LOAD_FAST_1 422
+#define _LOAD_FAST_2 423
+#define _LOAD_FAST_3 424
+#define _LOAD_FAST_4 425
+#define _LOAD_FAST_5 426
+#define _LOAD_FAST_6 427
+#define _LOAD_FAST_7 428
#define _LOAD_FAST_AND_CLEAR LOAD_FAST_AND_CLEAR
#define _LOAD_FAST_CHECK LOAD_FAST_CHECK
#define _LOAD_FAST_LOAD_FAST LOAD_FAST_LOAD_FAST
#define _LOAD_FROM_DICT_OR_DEREF LOAD_FROM_DICT_OR_DEREF
#define _LOAD_FROM_DICT_OR_GLOBALS LOAD_FROM_DICT_OR_GLOBALS
-#define _LOAD_GLOBAL 428
-#define _LOAD_GLOBAL_BUILTINS 429
-#define _LOAD_GLOBAL_MODULE 430
+#define _LOAD_GLOBAL 429
+#define _LOAD_GLOBAL_BUILTINS 430
+#define _LOAD_GLOBAL_MODULE 431
#define _LOAD_LOCALS LOAD_LOCALS
#define _LOAD_NAME LOAD_NAME
-#define _LOAD_SMALL_INT 431
-#define _LOAD_SMALL_INT_0 432
-#define _LOAD_SMALL_INT_1 433
-#define _LOAD_SMALL_INT_2 434
-#define _LOAD_SMALL_INT_3 435
+#define _LOAD_SMALL_INT 432
+#define _LOAD_SMALL_INT_0 433
+#define _LOAD_SMALL_INT_1 434
+#define _LOAD_SMALL_INT_2 435
+#define _LOAD_SMALL_INT_3 436
#define _LOAD_SPECIAL LOAD_SPECIAL
#define _LOAD_SUPER_ATTR_ATTR LOAD_SUPER_ATTR_ATTR
#define _LOAD_SUPER_ATTR_METHOD LOAD_SUPER_ATTR_METHOD
-#define _MAKE_CALLARGS_A_TUPLE 436
+#define _MAKE_CALLARGS_A_TUPLE 437
#define _MAKE_CELL MAKE_CELL
#define _MAKE_FUNCTION MAKE_FUNCTION
-#define _MAKE_WARM 437
+#define _MAKE_WARM 438
#define _MAP_ADD MAP_ADD
#define _MATCH_CLASS MATCH_CLASS
#define _MATCH_KEYS MATCH_KEYS
#define _MATCH_MAPPING MATCH_MAPPING
#define _MATCH_SEQUENCE MATCH_SEQUENCE
-#define _MAYBE_EXPAND_METHOD 438
-#define _MAYBE_EXPAND_METHOD_KW 439
-#define _MONITOR_CALL 440
-#define _MONITOR_CALL_KW 441
-#define _MONITOR_JUMP_BACKWARD 442
-#define _MONITOR_RESUME 443
+#define _MAYBE_EXPAND_METHOD 439
+#define _MAYBE_EXPAND_METHOD_KW 440
+#define _MONITOR_CALL 441
+#define _MONITOR_CALL_KW 442
+#define _MONITOR_JUMP_BACKWARD 443
+#define _MONITOR_RESUME 444
#define _NOP NOP
#define _POP_EXCEPT POP_EXCEPT
-#define _POP_JUMP_IF_FALSE 444
-#define _POP_JUMP_IF_TRUE 445
+#define _POP_JUMP_IF_FALSE 445
+#define _POP_JUMP_IF_TRUE 446
#define _POP_TOP POP_TOP
-#define _POP_TOP_LOAD_CONST_INLINE 446
-#define _POP_TOP_LOAD_CONST_INLINE_BORROW 447
+#define _POP_TOP_LOAD_CONST_INLINE 447
+#define _POP_TOP_LOAD_CONST_INLINE_BORROW 448
#define _PUSH_EXC_INFO PUSH_EXC_INFO
-#define _PUSH_FRAME 448
+#define _PUSH_FRAME 449
#define _PUSH_NULL PUSH_NULL
-#define _PUSH_NULL_CONDITIONAL 449
-#define _PY_FRAME_GENERAL 450
-#define _PY_FRAME_KW 451
-#define _QUICKEN_RESUME 452
-#define _REPLACE_WITH_TRUE 453
+#define _PUSH_NULL_CONDITIONAL 450
+#define _PY_FRAME_GENERAL 451
+#define _PY_FRAME_KW 452
+#define _QUICKEN_RESUME 453
+#define _REPLACE_WITH_TRUE 454
#define _RESUME_CHECK RESUME_CHECK
#define _RETURN_GENERATOR RETURN_GENERATOR
#define _RETURN_VALUE RETURN_VALUE
-#define _SAVE_RETURN_OFFSET 454
-#define _SEND 455
-#define _SEND_GEN_FRAME 456
+#define _SAVE_RETURN_OFFSET 455
+#define _SEND 456
+#define _SEND_GEN_FRAME 457
#define _SETUP_ANNOTATIONS SETUP_ANNOTATIONS
#define _SET_ADD SET_ADD
#define _SET_FUNCTION_ATTRIBUTE SET_FUNCTION_ATTRIBUTE
#define _SET_UPDATE SET_UPDATE
-#define _START_EXECUTOR 457
-#define _STORE_ATTR 458
-#define _STORE_ATTR_INSTANCE_VALUE 459
-#define _STORE_ATTR_SLOT 460
-#define _STORE_ATTR_WITH_HINT 461
+#define _START_EXECUTOR 458
+#define _STORE_ATTR 459
+#define _STORE_ATTR_INSTANCE_VALUE 460
+#define _STORE_ATTR_SLOT 461
+#define _STORE_ATTR_WITH_HINT 462
#define _STORE_DEREF STORE_DEREF
-#define _STORE_FAST 462
-#define _STORE_FAST_0 463
-#define _STORE_FAST_1 464
-#define _STORE_FAST_2 465
-#define _STORE_FAST_3 466
-#define _STORE_FAST_4 467
-#define _STORE_FAST_5 468
-#define _STORE_FAST_6 469
-#define _STORE_FAST_7 470
+#define _STORE_FAST 463
+#define _STORE_FAST_0 464
+#define _STORE_FAST_1 465
+#define _STORE_FAST_2 466
+#define _STORE_FAST_3 467
+#define _STORE_FAST_4 468
+#define _STORE_FAST_5 469
+#define _STORE_FAST_6 470
+#define _STORE_FAST_7 471
#define _STORE_FAST_LOAD_FAST STORE_FAST_LOAD_FAST
#define _STORE_FAST_STORE_FAST STORE_FAST_STORE_FAST
#define _STORE_GLOBAL STORE_GLOBAL
#define _STORE_NAME STORE_NAME
-#define _STORE_SLICE 471
-#define _STORE_SUBSCR 472
+#define _STORE_SLICE 472
+#define _STORE_SUBSCR 473
#define _STORE_SUBSCR_DICT STORE_SUBSCR_DICT
#define _STORE_SUBSCR_LIST_INT STORE_SUBSCR_LIST_INT
#define _SWAP SWAP
-#define _TIER2_RESUME_CHECK 473
-#define _TO_BOOL 474
+#define _TIER2_RESUME_CHECK 474
+#define _TO_BOOL 475
#define _TO_BOOL_BOOL TO_BOOL_BOOL
#define _TO_BOOL_INT TO_BOOL_INT
#define _TO_BOOL_LIST TO_BOOL_LIST
#define _UNARY_NEGATIVE UNARY_NEGATIVE
#define _UNARY_NOT UNARY_NOT
#define _UNPACK_EX UNPACK_EX
-#define _UNPACK_SEQUENCE 475
+#define _UNPACK_SEQUENCE 476
#define _UNPACK_SEQUENCE_LIST UNPACK_SEQUENCE_LIST
#define _UNPACK_SEQUENCE_TUPLE UNPACK_SEQUENCE_TUPLE
#define _UNPACK_SEQUENCE_TWO_TUPLE UNPACK_SEQUENCE_TWO_TUPLE
#define _WITH_EXCEPT_START WITH_EXCEPT_START
#define _YIELD_VALUE YIELD_VALUE
-#define MAX_UOP_ID 475
+#define MAX_UOP_ID 476
#ifdef __cplusplus
}
[_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] = 0,
+ [_ITER_NEXT_LIST_TIER_TWO] = HAS_EXIT_FLAG | HAS_ESCAPES_FLAG,
[_ITER_CHECK_TUPLE] = HAS_EXIT_FLAG,
[_GUARD_NOT_EXHAUSTED_TUPLE] = HAS_EXIT_FLAG,
[_ITER_NEXT_TUPLE] = 0,
[_ITER_CHECK_LIST] = "_ITER_CHECK_LIST",
[_ITER_CHECK_RANGE] = "_ITER_CHECK_RANGE",
[_ITER_CHECK_TUPLE] = "_ITER_CHECK_TUPLE",
- [_ITER_NEXT_LIST] = "_ITER_NEXT_LIST",
+ [_ITER_NEXT_LIST_TIER_TWO] = "_ITER_NEXT_LIST_TIER_TWO",
[_ITER_NEXT_RANGE] = "_ITER_NEXT_RANGE",
[_ITER_NEXT_TUPLE] = "_ITER_NEXT_TUPLE",
[_JUMP_TO_TOP] = "_JUMP_TO_TOP",
return 0;
case _GUARD_NOT_EXHAUSTED_LIST:
return 0;
- case _ITER_NEXT_LIST:
+ case _ITER_NEXT_LIST_TIER_TWO:
return 0;
case _ITER_CHECK_TUPLE:
return 0;
self.assert_specialized(load_const, "LOAD_CONST_MORTAL")
self.assert_no_opcode(load_const, "LOAD_CONST")
+ @cpython_only
+ @requires_specialization_ft
+ def test_for_iter(self):
+ L = list(range(10))
+ def for_iter_list():
+ for i in L:
+ self.assertIn(i, L)
+
+ for_iter_list()
+ self.assert_specialized(for_iter_list, "FOR_ITER_LIST")
+ self.assert_no_opcode(for_iter_list, "FOR_ITER")
+
+ t = tuple(range(10))
+ def for_iter_tuple():
+ for i in t:
+ self.assertIn(i, t)
+
+ for_iter_tuple()
+ self.assert_specialized(for_iter_tuple, "FOR_ITER_TUPLE")
+ self.assert_no_opcode(for_iter_tuple, "FOR_ITER")
+
+ r = range(10)
+ def for_iter_range():
+ for i in r:
+ self.assertIn(i, r)
+
+ for_iter_range()
+ self.assert_specialized(for_iter_range, "FOR_ITER_RANGE")
+ self.assert_no_opcode(for_iter_range, "FOR_ITER")
+
+ def for_iter_generator():
+ for i in (i for i in range(10)):
+ i + 1
+
+ for_iter_generator()
+ self.assert_specialized(for_iter_generator, "FOR_ITER_GEN")
+ self.assert_no_opcode(for_iter_generator, "FOR_ITER")
+
+
if __name__ == "__main__":
unittest.main()
return list_get_item_ref(list, i);
}
+#ifdef Py_GIL_DISABLED
+int
+_PyList_GetItemRefNoLock(PyListObject *list, Py_ssize_t i, _PyStackRef *result)
+{
+ assert(_Py_IsOwnedByCurrentThread((PyObject *)list) ||
+ _PyObject_GC_IS_SHARED(list));
+ if (!valid_index(i, PyList_GET_SIZE(list))) {
+ return 0;
+ }
+ PyObject **ob_item = _Py_atomic_load_ptr(&list->ob_item);
+ if (ob_item == NULL) {
+ return 0;
+ }
+ Py_ssize_t cap = list_capacity(ob_item);
+ assert(cap != -1);
+ if (!valid_index(i, cap)) {
+ return 0;
+ }
+ PyObject *obj = _Py_atomic_load_ptr(&ob_item[i]);
+ if (obj == NULL || !_Py_TryIncrefCompareStackRef(&ob_item[i], obj, result)) {
+ return -1;
+ }
+ return 1;
+}
+#endif
+
int
PyList_SetItem(PyObject *op, Py_ssize_t i,
PyObject *newitem)
};
specializing op(_SPECIALIZE_FOR_ITER, (counter/1, iter -- iter)) {
- #if ENABLE_SPECIALIZATION
+ #if ENABLE_SPECIALIZATION_FT
if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
next_instr = this_instr;
_Py_Specialize_ForIter(iter, next_instr, oparg);
}
OPCODE_DEFERRED_INC(FOR_ITER);
ADVANCE_ADAPTIVE_COUNTER(this_instr[1].counter);
- #endif /* ENABLE_SPECIALIZATION */
+ #endif /* ENABLE_SPECIALIZATION_FT */
}
replaced op(_FOR_ITER, (iter -- iter, next)) {
op(_ITER_CHECK_LIST, (iter -- iter)) {
- EXIT_IF(Py_TYPE(PyStackRef_AsPyObjectBorrow(iter)) != &PyListIter_Type);
+ PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
+ EXIT_IF(Py_TYPE(iter_o) != &PyListIter_Type);
+#ifdef Py_GIL_DISABLED
+ EXIT_IF(!_PyObject_IsUniquelyReferenced(iter_o));
+ _PyListIterObject *it = (_PyListIterObject *)iter_o;
+ EXIT_IF(!_Py_IsOwnedByCurrentThread((PyObject *)it->it_seq) ||
+ !_PyObject_GC_IS_SHARED(it->it_seq));
+#endif
}
replaced op(_ITER_JUMP_LIST, (iter -- iter)) {
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyListIterObject *it = (_PyListIterObject *)iter_o;
assert(Py_TYPE(iter_o) == &PyListIter_Type);
+// For free-threaded Python, the loop exit can happen at any point during
+// item retrieval, so it doesn't make much sense to check and jump
+// separately before item retrieval. Any length check we do here can be
+// invalid by the time we actually try to fetch the item.
+#ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced(iter_o));
+ (void)iter_o;
+#else
+ _PyListIterObject *it = (_PyListIterObject *)iter_o;
STAT_INC(FOR_ITER, hit);
PyListObject *seq = it->it_seq;
if (seq == NULL || (size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) {
it->it_index = -1;
- #ifndef Py_GIL_DISABLED
if (seq != NULL) {
it->it_seq = NULL;
Py_DECREF(seq);
}
- #endif
/* Jump forward oparg, then skip following END_FOR instruction */
JUMPBY(oparg + 1);
DISPATCH();
}
+#endif
}
// Only used by Tier 2
op(_GUARD_NOT_EXHAUSTED_LIST, (iter -- iter)) {
+#ifndef Py_GIL_DISABLED
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
_PyListIterObject *it = (_PyListIterObject *)iter_o;
assert(Py_TYPE(iter_o) == &PyListIter_Type);
it->it_index = -1;
EXIT_IF(1);
}
+#endif
+ }
+
+ replaced op(_ITER_NEXT_LIST, (iter -- iter, next)) {
+ PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
+ _PyListIterObject *it = (_PyListIterObject *)iter_o;
+ assert(Py_TYPE(iter_o) == &PyListIter_Type);
+ PyListObject *seq = it->it_seq;
+ assert(seq);
+#ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced(iter_o));
+ assert(_Py_IsOwnedByCurrentThread((PyObject *)seq) ||
+ _PyObject_GC_IS_SHARED(seq));
+ STAT_INC(FOR_ITER, hit);
+ int result = _PyList_GetItemRefNoLock(seq, it->it_index, &next);
+ // A negative result means we lost a race with another thread
+ // and we need to take the slow path.
+ DEOPT_IF(result < 0);
+ if (result == 0) {
+ it->it_index = -1;
+ /* Jump forward oparg, then skip following END_FOR instruction */
+ JUMPBY(oparg + 1);
+ DISPATCH();
+ }
+ it->it_index++;
+#else
+ assert(it->it_index < PyList_GET_SIZE(seq));
+ next = PyStackRef_FromPyObjectNew(PyList_GET_ITEM(seq, it->it_index++));
+#endif
}
- op(_ITER_NEXT_LIST, (iter -- iter, next)) {
+ // Only used by Tier 2
+ op(_ITER_NEXT_LIST_TIER_TWO, (iter -- iter, next)) {
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
_PyListIterObject *it = (_PyListIterObject *)iter_o;
assert(Py_TYPE(iter_o) == &PyListIter_Type);
PyListObject *seq = it->it_seq;
assert(seq);
+#ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced(iter_o));
+ assert(_Py_IsOwnedByCurrentThread((PyObject *)seq) ||
+ _PyObject_GC_IS_SHARED(seq));
+ STAT_INC(FOR_ITER, hit);
+ int result = _PyList_GetItemRefNoLock(seq, it->it_index, &next);
+ // A negative result means we lost a race with another thread
+ // and we need to take the slow path.
+ EXIT_IF(result < 0);
+ if (result == 0) {
+ it->it_index = -1;
+ EXIT_IF(1);
+ }
+ it->it_index++;
+#else
assert(it->it_index < PyList_GET_SIZE(seq));
next = PyStackRef_FromPyObjectNew(PyList_GET_ITEM(seq, it->it_index++));
+#endif
}
macro(FOR_ITER_LIST) =
_ITER_NEXT_LIST;
op(_ITER_CHECK_TUPLE, (iter -- iter)) {
- EXIT_IF(Py_TYPE(PyStackRef_AsPyObjectBorrow(iter)) != &PyTupleIter_Type);
+ PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
+ EXIT_IF(Py_TYPE(iter_o) != &PyTupleIter_Type);
+#ifdef Py_GIL_DISABLED
+ EXIT_IF(!_PyObject_IsUniquelyReferenced(iter_o));
+#endif
}
replaced op(_ITER_JUMP_TUPLE, (iter -- iter)) {
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
+ (void)iter_o;
assert(Py_TYPE(iter_o) == &PyTupleIter_Type);
+#ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced(iter_o));
+#endif
+ _PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
STAT_INC(FOR_ITER, hit);
PyTupleObject *seq = it->it_seq;
- if (seq == NULL || it->it_index >= PyTuple_GET_SIZE(seq)) {
+ if (seq == NULL || (size_t)it->it_index >= (size_t)PyTuple_GET_SIZE(seq)) {
+#ifndef Py_GIL_DISABLED
if (seq != NULL) {
it->it_seq = NULL;
Py_DECREF(seq);
}
+#endif
/* Jump forward oparg, then skip following END_FOR instruction */
JUMPBY(oparg + 1);
DISPATCH();
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
_PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
assert(Py_TYPE(iter_o) == &PyTupleIter_Type);
+#ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced(iter_o));
+#endif
PyTupleObject *seq = it->it_seq;
EXIT_IF(seq == NULL);
EXIT_IF(it->it_index >= PyTuple_GET_SIZE(seq));
_PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
assert(Py_TYPE(iter_o) == &PyTupleIter_Type);
PyTupleObject *seq = it->it_seq;
+#ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced(iter_o));
+#endif
assert(seq);
assert(it->it_index < PyTuple_GET_SIZE(seq));
next = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(seq, it->it_index++));
op(_ITER_CHECK_RANGE, (iter -- iter)) {
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
EXIT_IF(Py_TYPE(r) != &PyRangeIter_Type);
+#ifdef Py_GIL_DISABLED
+ EXIT_IF(!_PyObject_IsUniquelyReferenced((PyObject *)r));
+#endif
}
replaced op(_ITER_JUMP_RANGE, (iter -- iter)) {
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
assert(Py_TYPE(r) == &PyRangeIter_Type);
+#ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced((PyObject *)r));
+#endif
STAT_INC(FOR_ITER, hit);
if (r->len <= 0) {
// Jump over END_FOR instruction.
op(_ITER_NEXT_RANGE, (iter -- iter, next)) {
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
assert(Py_TYPE(r) == &PyRangeIter_Type);
+#ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced((PyObject *)r));
+#endif
assert(r->len > 0);
long value = r->start;
r->start = value + r->step;
op(_FOR_ITER_GEN_FRAME, (iter -- iter, gen_frame: _PyInterpreterFrame*)) {
PyGenObject *gen = (PyGenObject *)PyStackRef_AsPyObjectBorrow(iter);
DEOPT_IF(Py_TYPE(gen) != &PyGen_Type);
+#ifdef Py_GIL_DISABLED
+ // Since generators can't be used by multiple threads anyway we
+ // don't need to deopt here, but this lets us work on making
+ // generators thread-safe without necessarily having to
+ // specialize them thread-safely as well.
+ DEOPT_IF(!_PyObject_IsUniquelyReferenced((PyObject *)gen));
+#endif
DEOPT_IF(gen->gi_frame_state >= FRAME_EXECUTING);
STAT_INC(FOR_ITER, hit);
gen_frame = &gen->gi_iframe;
case _ITER_CHECK_LIST: {
_PyStackRef iter;
iter = stack_pointer[-1];
- if (Py_TYPE(PyStackRef_AsPyObjectBorrow(iter)) != &PyListIter_Type) {
+ PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
+ if (Py_TYPE(iter_o) != &PyListIter_Type) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
+ #ifdef Py_GIL_DISABLED
+ if (!_PyObject_IsUniquelyReferenced(iter_o)) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
+ _PyListIterObject *it = (_PyListIterObject *)iter_o;
+ if (!_Py_IsOwnedByCurrentThread((PyObject *)it->it_seq) ||
+ !_PyObject_GC_IS_SHARED(it->it_seq)) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
+ #endif
break;
}
case _GUARD_NOT_EXHAUSTED_LIST: {
_PyStackRef iter;
iter = stack_pointer[-1];
+ #ifndef Py_GIL_DISABLED
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
_PyListIterObject *it = (_PyListIterObject *)iter_o;
assert(Py_TYPE(iter_o) == &PyListIter_Type);
JUMP_TO_JUMP_TARGET();
}
}
+ #endif
break;
}
- case _ITER_NEXT_LIST: {
+ /* _ITER_NEXT_LIST is not a viable micro-op for tier 2 because it is replaced */
+
+ case _ITER_NEXT_LIST_TIER_TWO: {
_PyStackRef iter;
_PyStackRef next;
iter = stack_pointer[-1];
assert(Py_TYPE(iter_o) == &PyListIter_Type);
PyListObject *seq = it->it_seq;
assert(seq);
+ #ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced(iter_o));
+ assert(_Py_IsOwnedByCurrentThread((PyObject *)seq) ||
+ _PyObject_GC_IS_SHARED(seq));
+ STAT_INC(FOR_ITER, hit);
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ int result = _PyList_GetItemRefNoLock(seq, it->it_index, &next);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ // A negative result means we lost a race with another thread
+ // and we need to take the slow path.
+ if (result < 0) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
+ if (result == 0) {
+ it->it_index = -1;
+ if (1) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
+ }
+ it->it_index++;
+ #else
assert(it->it_index < PyList_GET_SIZE(seq));
next = PyStackRef_FromPyObjectNew(PyList_GET_ITEM(seq, it->it_index++));
+ #endif
stack_pointer[0] = next;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
case _ITER_CHECK_TUPLE: {
_PyStackRef iter;
iter = stack_pointer[-1];
- if (Py_TYPE(PyStackRef_AsPyObjectBorrow(iter)) != &PyTupleIter_Type) {
+ PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
+ if (Py_TYPE(iter_o) != &PyTupleIter_Type) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
+ #ifdef Py_GIL_DISABLED
+ if (!_PyObject_IsUniquelyReferenced(iter_o)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
+ #endif
break;
}
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
_PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
assert(Py_TYPE(iter_o) == &PyTupleIter_Type);
+ #ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced(iter_o));
+ #endif
PyTupleObject *seq = it->it_seq;
if (seq == NULL) {
UOP_STAT_INC(uopcode, miss);
_PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
assert(Py_TYPE(iter_o) == &PyTupleIter_Type);
PyTupleObject *seq = it->it_seq;
+ #ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced(iter_o));
+ #endif
assert(seq);
assert(it->it_index < PyTuple_GET_SIZE(seq));
next = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(seq, it->it_index++));
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
+ #ifdef Py_GIL_DISABLED
+ if (!_PyObject_IsUniquelyReferenced((PyObject *)r)) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
+ #endif
break;
}
iter = stack_pointer[-1];
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
assert(Py_TYPE(r) == &PyRangeIter_Type);
+ #ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced((PyObject *)r));
+ #endif
assert(r->len > 0);
long value = r->start;
r->start = value + r->step;
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
+ #ifdef Py_GIL_DISABLED
+ // Since generators can't be used by multiple threads anyway we
+ // don't need to deopt here, but this lets us work on making
+ // generators thread-safe without necessarily having to
+ // specialize them thread-safely as well.
+ if (!_PyObject_IsUniquelyReferenced((PyObject *)gen)) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
+ #endif
if (gen->gi_frame_state >= FRAME_EXECUTING) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
iter = stack_pointer[-1];
uint16_t counter = read_u16(&this_instr[1].cache);
(void)counter;
- #if ENABLE_SPECIALIZATION
+ #if ENABLE_SPECIALIZATION_FT
if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
next_instr = this_instr;
_PyFrame_SetStackPointer(frame, stack_pointer);
}
OPCODE_DEFERRED_INC(FOR_ITER);
ADVANCE_ADAPTIVE_COUNTER(this_instr[1].counter);
- #endif /* ENABLE_SPECIALIZATION */
+ #endif /* ENABLE_SPECIALIZATION_FT */
}
// _FOR_ITER
{
assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
JUMP_TO_PREDICTED(FOR_ITER);
}
+ #ifdef Py_GIL_DISABLED
+ // Since generators can't be used by multiple threads anyway we
+ // don't need to deopt here, but this lets us work on making
+ // generators thread-safe without necessarily having to
+ // specialize them thread-safely as well.
+ if (!_PyObject_IsUniquelyReferenced((PyObject *)gen)) {
+ UPDATE_MISS_STATS(FOR_ITER);
+ assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
+ JUMP_TO_PREDICTED(FOR_ITER);
+ }
+ #endif
if (gen->gi_frame_state >= FRAME_EXECUTING) {
UPDATE_MISS_STATS(FOR_ITER);
assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
// _ITER_CHECK_LIST
{
iter = stack_pointer[-1];
- if (Py_TYPE(PyStackRef_AsPyObjectBorrow(iter)) != &PyListIter_Type) {
+ PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
+ if (Py_TYPE(iter_o) != &PyListIter_Type) {
UPDATE_MISS_STATS(FOR_ITER);
assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
JUMP_TO_PREDICTED(FOR_ITER);
}
+ #ifdef Py_GIL_DISABLED
+ if (!_PyObject_IsUniquelyReferenced(iter_o)) {
+ UPDATE_MISS_STATS(FOR_ITER);
+ assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
+ JUMP_TO_PREDICTED(FOR_ITER);
+ }
+ _PyListIterObject *it = (_PyListIterObject *)iter_o;
+ if (!_Py_IsOwnedByCurrentThread((PyObject *)it->it_seq) ||
+ !_PyObject_GC_IS_SHARED(it->it_seq)) {
+ UPDATE_MISS_STATS(FOR_ITER);
+ assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
+ JUMP_TO_PREDICTED(FOR_ITER);
+ }
+ #endif
}
// _ITER_JUMP_LIST
{
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyListIterObject *it = (_PyListIterObject *)iter_o;
assert(Py_TYPE(iter_o) == &PyListIter_Type);
+ // For free-threaded Python, the loop exit can happen at any point during
+ // item retrieval, so it doesn't make much sense to check and jump
+ // separately before item retrieval. Any length check we do here can be
+ // invalid by the time we actually try to fetch the item.
+ #ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced(iter_o));
+ (void)iter_o;
+ #else
+ _PyListIterObject *it = (_PyListIterObject *)iter_o;
STAT_INC(FOR_ITER, hit);
PyListObject *seq = it->it_seq;
if (seq == NULL || (size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) {
it->it_index = -1;
- #ifndef Py_GIL_DISABLED
if (seq != NULL) {
it->it_seq = NULL;
_PyFrame_SetStackPointer(frame, stack_pointer);
Py_DECREF(seq);
stack_pointer = _PyFrame_GetStackPointer(frame);
}
- #endif
/* Jump forward oparg, then skip following END_FOR instruction */
JUMPBY(oparg + 1);
DISPATCH();
}
+ #endif
}
// _ITER_NEXT_LIST
{
assert(Py_TYPE(iter_o) == &PyListIter_Type);
PyListObject *seq = it->it_seq;
assert(seq);
+ #ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced(iter_o));
+ assert(_Py_IsOwnedByCurrentThread((PyObject *)seq) ||
+ _PyObject_GC_IS_SHARED(seq));
+ STAT_INC(FOR_ITER, hit);
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ int result = _PyList_GetItemRefNoLock(seq, it->it_index, &next);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ // A negative result means we lost a race with another thread
+ // and we need to take the slow path.
+ if (result < 0) {
+ UPDATE_MISS_STATS(FOR_ITER);
+ assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
+ JUMP_TO_PREDICTED(FOR_ITER);
+ }
+ if (result == 0) {
+ it->it_index = -1;
+ /* Jump forward oparg, then skip following END_FOR instruction */
+ JUMPBY(oparg + 1);
+ DISPATCH();
+ }
+ it->it_index++;
+ #else
assert(it->it_index < PyList_GET_SIZE(seq));
next = PyStackRef_FromPyObjectNew(PyList_GET_ITEM(seq, it->it_index++));
+ #endif
}
stack_pointer[0] = next;
stack_pointer += 1;
assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
JUMP_TO_PREDICTED(FOR_ITER);
}
+ #ifdef Py_GIL_DISABLED
+ if (!_PyObject_IsUniquelyReferenced((PyObject *)r)) {
+ UPDATE_MISS_STATS(FOR_ITER);
+ assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
+ JUMP_TO_PREDICTED(FOR_ITER);
+ }
+ #endif
}
// _ITER_JUMP_RANGE
{
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
assert(Py_TYPE(r) == &PyRangeIter_Type);
+ #ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced((PyObject *)r));
+ #endif
STAT_INC(FOR_ITER, hit);
if (r->len <= 0) {
// Jump over END_FOR instruction.
{
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
assert(Py_TYPE(r) == &PyRangeIter_Type);
+ #ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced((PyObject *)r));
+ #endif
assert(r->len > 0);
long value = r->start;
r->start = value + r->step;
// _ITER_CHECK_TUPLE
{
iter = stack_pointer[-1];
- if (Py_TYPE(PyStackRef_AsPyObjectBorrow(iter)) != &PyTupleIter_Type) {
+ PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
+ if (Py_TYPE(iter_o) != &PyTupleIter_Type) {
UPDATE_MISS_STATS(FOR_ITER);
assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
JUMP_TO_PREDICTED(FOR_ITER);
}
+ #ifdef Py_GIL_DISABLED
+ if (!_PyObject_IsUniquelyReferenced(iter_o)) {
+ UPDATE_MISS_STATS(FOR_ITER);
+ assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
+ JUMP_TO_PREDICTED(FOR_ITER);
+ }
+ #endif
}
// _ITER_JUMP_TUPLE
{
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
+ (void)iter_o;
assert(Py_TYPE(iter_o) == &PyTupleIter_Type);
+ #ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced(iter_o));
+ #endif
+ _PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
STAT_INC(FOR_ITER, hit);
PyTupleObject *seq = it->it_seq;
- if (seq == NULL || it->it_index >= PyTuple_GET_SIZE(seq)) {
+ if (seq == NULL || (size_t)it->it_index >= (size_t)PyTuple_GET_SIZE(seq)) {
+ #ifndef Py_GIL_DISABLED
if (seq != NULL) {
it->it_seq = NULL;
_PyFrame_SetStackPointer(frame, stack_pointer);
Py_DECREF(seq);
stack_pointer = _PyFrame_GetStackPointer(frame);
}
+ #endif
/* Jump forward oparg, then skip following END_FOR instruction */
JUMPBY(oparg + 1);
DISPATCH();
_PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
assert(Py_TYPE(iter_o) == &PyTupleIter_Type);
PyTupleObject *seq = it->it_seq;
+ #ifdef Py_GIL_DISABLED
+ assert(_PyObject_IsUniquelyReferenced(iter_o));
+ #endif
assert(seq);
assert(it->it_index < PyTuple_GET_SIZE(seq));
next = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(seq, it->it_index++));
[_ITER_JUMP_LIST] = _GUARD_NOT_EXHAUSTED_LIST,
[_ITER_JUMP_TUPLE] = _GUARD_NOT_EXHAUSTED_TUPLE,
[_FOR_ITER] = _FOR_ITER_TIER_TWO,
+ [_ITER_NEXT_LIST] = _ITER_NEXT_LIST_TIER_TWO,
};
static const uint8_t
break;
}
- case _ITER_NEXT_LIST: {
+ /* _ITER_NEXT_LIST is not a viable micro-op for tier 2 */
+
+ case _ITER_NEXT_LIST_TIER_TWO: {
JitOptSymbol *next;
next = sym_new_not_null(ctx);
stack_pointer[0] = next;
void
_Py_Specialize_ForIter(_PyStackRef iter, _Py_CODEUNIT *instr, int oparg)
{
- assert(ENABLE_SPECIALIZATION);
+ assert(ENABLE_SPECIALIZATION_FT);
assert(_PyOpcode_Caches[FOR_ITER] == INLINE_CACHE_ENTRIES_FOR_ITER);
- _PyForIterCache *cache = (_PyForIterCache *)(instr + 1);
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
PyTypeObject *tp = Py_TYPE(iter_o);
+#ifdef Py_GIL_DISABLED
+ // Only specialize for uniquely referenced iterators, so that we know
+ // they're only referenced by this one thread. This is more limiting
+ // than we need (even `it = iter(mylist); for item in it:` won't get
+ // specialized) but we don't have a way to check whether we're the only
+ // _thread_ who has access to the object.
+ if (!_PyObject_IsUniquelyReferenced(iter_o))
+ goto failure;
+#endif
if (tp == &PyListIter_Type) {
- instr->op.code = FOR_ITER_LIST;
- goto success;
+#ifdef Py_GIL_DISABLED
+ _PyListIterObject *it = (_PyListIterObject *)iter_o;
+ if (!_Py_IsOwnedByCurrentThread((PyObject *)it->it_seq) &&
+ !_PyObject_GC_IS_SHARED(it->it_seq)) {
+ // Maybe this should just set GC_IS_SHARED in a critical
+ // section, instead of leaving it to the first iteration?
+ goto failure;
+ }
+#endif
+ specialize(instr, FOR_ITER_LIST);
+ return;
}
else if (tp == &PyTupleIter_Type) {
- instr->op.code = FOR_ITER_TUPLE;
- goto success;
+ specialize(instr, FOR_ITER_TUPLE);
+ return;
}
else if (tp == &PyRangeIter_Type) {
- instr->op.code = FOR_ITER_RANGE;
- goto success;
+ specialize(instr, FOR_ITER_RANGE);
+ return;
}
else if (tp == &PyGen_Type && oparg <= SHRT_MAX) {
+ // Generators are very much not thread-safe, so don't worry about
+ // the specialization not being thread-safe.
assert(instr[oparg + INLINE_CACHE_ENTRIES_FOR_ITER + 1].op.code == END_FOR ||
instr[oparg + INLINE_CACHE_ENTRIES_FOR_ITER + 1].op.code == INSTRUMENTED_END_FOR
);
/* Don't specialize if PEP 523 is active */
- if (_PyInterpreterState_GET()->eval_frame) {
- SPECIALIZATION_FAIL(FOR_ITER, SPEC_FAIL_OTHER);
+ if (_PyInterpreterState_GET()->eval_frame)
goto failure;
- }
- instr->op.code = FOR_ITER_GEN;
- goto success;
+ specialize(instr, FOR_ITER_GEN);
+ return;
}
+failure:
SPECIALIZATION_FAIL(FOR_ITER,
_PySpecialization_ClassifyIterator(iter_o));
-failure:
- STAT_INC(FOR_ITER, failure);
- instr->op.code = FOR_ITER;
- cache->counter = adaptive_counter_backoff(cache->counter);
- return;
-success:
- STAT_INC(FOR_ITER, success);
- cache->counter = adaptive_counter_cooldown();
+ unspecialize(instr);
}
void
"_PyLong_IsNonNegativeCompact",
"_PyLong_IsZero",
"_PyManagedDictPointer_IsValues",
+ "_PyObject_GC_IS_SHARED",
"_PyObject_GC_IS_TRACKED",
"_PyObject_GC_MAY_BE_TRACKED",
"_PyObject_GC_TRACK",
"_PyObject_GetManagedDict",
"_PyObject_InlineValues",
+ "_PyObject_IsUniquelyReferenced",
"_PyObject_ManagedDictPointer",
"_PyThreadState_HasStackSpace",
"_PyTuple_FromStackRefStealOnSuccess",
"_Py_DECREF_NO_DEALLOC",
"_Py_ID",
"_Py_IsImmortal",
+ "_Py_IsOwnedByCurrentThread",
"_Py_LeaveRecursiveCallPy",
"_Py_LeaveRecursiveCallTstate",
"_Py_NewRef",