gh-115999: Enable specialization of CALL instructions in free-threaded builds (#127123)

The CALL family of instructions were mostly thread-safe already and only required a small number of changes, which are documented below.

A few changes were needed to make CALL_ALLOC_AND_ENTER_INIT thread-safe:

Added _PyType_LookupRefAndVersion, which returns the type version corresponding to the returned ref.

Added _PyType_CacheInitForSpecialization, which takes an init method and the corresponding type version and only populates the specialization cache if the current type version matches the supplied version. This prevents potentially caching a stale value in free-threaded builds if we race with an update to __init__.

Only cache __init__ functions that are deferred in free-threaded builds. This ensures that the reference to __init__ that is stored in the specialization cache is valid if the type version guard in _CHECK_AND_ALLOCATE_OBJECT passes.
Fix a bug in _CREATE_INIT_FRAME where the frame is pushed to the stack on failure.

A few other miscellaneous changes were also needed:

Use {LOCK,UNLOCK}_OBJECT in LIST_APPEND. This ensures that the list's per-object lock is held while we are appending to it.

Add missing co_tlbc for _Py_InitCleanup.

Stop/start the world around setting the eval frame hook. This allows us to read interp->eval_frame non-atomically and preserves the behavior of _CHECK_PEP_523 documented below.
This commit is contained in:
mpage 2024-12-03 11:20:20 -08:00 committed by GitHub
parent fc5a0dc224
commit dabcecfd6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 220 additions and 92 deletions

View file

@ -11,7 +11,7 @@ import types
import unittest
import test.support
from test.support import requires_specialization, script_helper
from test.support import requires_specialization_ft, script_helper
from test.support.import_helper import import_module
_testcapi = test.support.import_helper.import_module("_testcapi")
@ -850,6 +850,13 @@ class ReturnRecorder:
def __call__(self, code, offset, val):
self.events.append(("return", code.co_name, val))
# gh-127274: CALL_ALLOC_AND_ENTER_INIT will only cache __init__ methods that
# are deferred. We only defer functions defined at the top-level.
class ValueErrorRaiser:
def __init__(self):
raise ValueError()
class ExceptionMonitoringTest(CheckEvents):
exception_recorders = (
@ -1045,16 +1052,12 @@ class ExceptionMonitoringTest(CheckEvents):
)
self.assertEqual(events[0], ("throw", IndexError))
@requires_specialization
@requires_specialization_ft
def test_no_unwind_for_shim_frame(self):
class B:
def __init__(self):
raise ValueError()
def f():
try:
return B()
return ValueErrorRaiser()
except ValueError:
pass