From 77163cb3d928c86a418e1e6daec5f243a850892c Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Fri, 29 Nov 2024 12:09:59 -0800 Subject: [PATCH 1/3] gh-115999 Add free-threaded specialization for `SEND`. No additional thread safety changes are required. Note that sending to a generator that is shared between threads is not safe in the free-threaded build. --- Lib/test/test_opcache.py | 42 ++++++++++++++++++++++++++++++++++++++ Python/bytecodes.c | 4 ++-- Python/generated_cases.c.h | 4 ++-- Python/specialize.c | 13 ++++-------- 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 1a6eac236009c3..11f4d6091d8aac 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -1272,6 +1272,48 @@ def g(): self.assert_specialized(g, "CONTAINS_OP_SET") self.assert_no_opcode(g, "CONTAINS_OP") + @cpython_only + @requires_specialization_ft + def test_send_with(self): + def run_async(coro): + while True: + try: + coro.send(None) + except StopIteration: + break + + class CM: + async def __aenter__(self): + return self + + async def __aexit__(self, *exc): + pass + + async def f(): + for i in range(100): + async with CM(): + x = 1 + + run_async(f()) + # Note there are still unspecialized "SEND" opcodes in the + # cleanup paths of the 'with' statement. + self.assert_specialized(f, "SEND_GEN") + + @cpython_only + @requires_specialization_ft + def test_send_yield_from(self): + def g(): + yield None + + def f(): + yield from g() + + for i in range(100): + list(f()) + + self.assert_specialized(f, "SEND_GEN") + self.assert_no_opcode(f, "SEND") + @cpython_only @requires_specialization_ft def test_to_bool(self): diff --git a/Python/bytecodes.c b/Python/bytecodes.c index a14b32b8108be8..59d39f937d0383 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1111,7 +1111,7 @@ dummy_func( }; specializing op(_SPECIALIZE_SEND, (counter/1, receiver, unused -- receiver, unused)) { - #if ENABLE_SPECIALIZATION + #if ENABLE_SPECIALIZATION_FT if (ADAPTIVE_COUNTER_TRIGGERS(counter)) { next_instr = this_instr; _Py_Specialize_Send(receiver, next_instr); @@ -1119,7 +1119,7 @@ dummy_func( } OPCODE_DEFERRED_INC(SEND); ADVANCE_ADAPTIVE_COUNTER(this_instr[1].counter); - #endif /* ENABLE_SPECIALIZATION */ + #endif /* ENABLE_SPECIALIZATION_FT */ } op(_SEND, (receiver, v -- receiver, retval)) { diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index c9a5132269398c..ae8bd89f79afcd 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -7057,7 +7057,7 @@ receiver = stack_pointer[-2]; 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); @@ -7067,7 +7067,7 @@ } OPCODE_DEFERRED_INC(SEND); ADVANCE_ADAPTIVE_COUNTER(this_instr[1].counter); - #endif /* ENABLE_SPECIALIZATION */ + #endif /* ENABLE_SPECIALIZATION_FT */ } // _SEND { diff --git a/Python/specialize.c b/Python/specialize.c index 172dae7d374602..cebe3b6e19c71c 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -2636,28 +2636,23 @@ _Py_Specialize_Send(_PyStackRef receiver_st, _Py_CODEUNIT *instr) { PyObject *receiver = PyStackRef_AsPyObjectBorrow(receiver_st); - assert(ENABLE_SPECIALIZATION); + assert(ENABLE_SPECIALIZATION_FT); assert(_PyOpcode_Caches[SEND] == INLINE_CACHE_ENTRIES_SEND); - _PySendCache *cache = (_PySendCache *)(instr + 1); PyTypeObject *tp = Py_TYPE(receiver); if (tp == &PyGen_Type || tp == &PyCoro_Type) { if (_PyInterpreterState_GET()->eval_frame) { SPECIALIZATION_FAIL(SEND, SPEC_FAIL_OTHER); goto failure; } - instr->op.code = SEND_GEN; + specialize(instr, SEND_GEN); goto success; } SPECIALIZATION_FAIL(SEND, _PySpecialization_ClassifyIterator(receiver)); failure: - STAT_INC(SEND, failure); - instr->op.code = SEND; - cache->counter = adaptive_counter_backoff(cache->counter); - return; + unspecialize(instr); success: - STAT_INC(SEND, success); - cache->counter = adaptive_counter_cooldown(); + return; } #ifdef Py_STATS From e517ccb8e08fa7762c1fe080ea7cfc9d3c6b990e Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Mon, 2 Dec 2024 11:34:10 -0800 Subject: [PATCH 2/3] Use more descriptive function names in tests. --- Lib/test/test_opcache.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 11f4d6091d8aac..ab660eb3a8e81a 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -1289,15 +1289,15 @@ async def __aenter__(self): async def __aexit__(self, *exc): pass - async def f(): + async def send_with(): for i in range(100): async with CM(): x = 1 - run_async(f()) + run_async(send_with()) # Note there are still unspecialized "SEND" opcodes in the # cleanup paths of the 'with' statement. - self.assert_specialized(f, "SEND_GEN") + self.assert_specialized(send_with, "SEND_GEN") @cpython_only @requires_specialization_ft @@ -1305,14 +1305,14 @@ def test_send_yield_from(self): def g(): yield None - def f(): + def send_yield_from(): yield from g() for i in range(100): - list(f()) + list(send_yield_from()) - self.assert_specialized(f, "SEND_GEN") - self.assert_no_opcode(f, "SEND") + self.assert_specialized(send_yield_from, "SEND_GEN") + self.assert_no_opcode(send_yield_from, "SEND") @cpython_only @requires_specialization_ft From 40dac97909cbcd8bbc406e3a5ea075f1c05ca755 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Tue, 3 Dec 2024 09:29:37 -0800 Subject: [PATCH 3/3] Small code simplification. --- Python/specialize.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Python/specialize.c b/Python/specialize.c index cebe3b6e19c71c..1ec502c3cf1668 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -2645,14 +2645,12 @@ _Py_Specialize_Send(_PyStackRef receiver_st, _Py_CODEUNIT *instr) goto failure; } specialize(instr, SEND_GEN); - goto success; + return; } SPECIALIZATION_FAIL(SEND, _PySpecialization_ClassifyIterator(receiver)); failure: unspecialize(instr); -success: - return; } #ifdef Py_STATS