8000 gh-118527: Intern code consts in free-threaded build (#118667) · python/cpython@723d4d2 · GitHub
[go: up one dir, main page]

Skip to content

Commit 723d4d2

Browse files
authored
gh-118527: Intern code consts in free-threaded build (#118667)
We already intern and immortalize most string constants. In the free-threaded build, other constants can be a source of reference count contention because they are shared by all threads running the same code objects.
1 parent 8d8275b commit 723d4d2

14 files changed

+375
-18
lines changed

Include/internal/pycore_code.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11+
#include "pycore_lock.h" // PyMutex
12+
1113

1214
// We hide some of the newer PyCodeObject fields behind macros.
1315
// This helps with backporting certain changes to 3.12.
@@ -16,6 +18,14 @@ extern "C" {
1618
#define _PyCode_HAS_INSTRUMENTATION(CODE) \
1719
(CODE->_co_instrumentation_version > 0)
1820

21+
struct _py_code_state {
22+
PyMutex mutex;
23+
// Interned constants from code objects. Used by the free-threaded build.
24+
struct _Py_hashtable_t *constants;
25+
};
26+
27+
extern PyStatus _PyCode_Init(PyInterpreterState *interp);
28+
extern void _PyCode_Fini(PyInterpreterState *interp);
1929

2030
#define CODE_MAX_WATCHERS 8
2131

Include/internal/pycore_interp.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ struct _is {
245245
struct _Py_long_state long_state;
246246
struct _dtoa_state dtoa;
247247
struct _py_func_state func_state;
248+
struct _py_code_state code_state;
248249

249250
struct _Py_dict_state dict_state;
250251
struct _Py_exc_state exc_state;

Include/internal/pycore_setobject.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ PyAPI_DATA(PyObject *) _PySet_Dummy;
3030

3131
PyAPI_FUNC(int) _PySet_Contains(PySetObject *so, PyObject *key);
3232

33+
// Clears the set without acquiring locks. Used by _PyCode_Fini.
34+
extern void _PySet_ClearInternal(PySetObject *so);
35+
3336
#ifdef __cplusplus
3437
}
3538
#endif

Lib/test/support/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,15 @@ def suppress_immortalization(suppress=True):
535535
finally:
536536
_testinternalcapi.set_immortalize_deferred(*old_values)
537537

538+
def skip_if_suppress_immortalization():
539+
try:
540+
import _testinternalcapi
541+
except ImportError:
542+
return
543+
return unittest.skipUnless(_testinternalcapi.get_immortalize_deferred(),
544+
"requires immortalization of deferred objects")
545+
546+
538547
MS_WINDOWS = (sys.platform == 'win32')
539548

540549
# Is not actually used in tests, but is kept for compatibility.

Lib/test/test_code.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@
142142
from test.support import (cpython_only,
143143
check_impl_detail, requires_debug_ranges,
144144
gc_collect, Py_GIL_DISABLED,
145-
suppress_immortalization)
145+
suppress_immortalization,
146+
skip_if_suppress_immortalization)
146147
from test.support.script_helper import assert_python_ok
147148
from test.support import threading_helper, import_helper
148149
from test.support.bytecode_helper import instructions_with_positions
@@ -570,11 +571,31 @@ def f(a='str_value'):
570571
self.assertIsInterned(f())
571572

572573
@cpython_only
574+
@unittest.skipIf(Py_GIL_DISABLED, "free-threaded build interns all string constants")
573575
def test_interned_string_with_null(self):
574576
co = compile(r'res = "str\0value!"', '?', 'exec')
575577
v = self.find_const(co.co_consts, 'str\0value!')
576578
self.assertIsNotInterned(v)
577579

580+
@cpython_only
581+
@unittest.skipUnless(Py_GIL_DISABLED, "does not intern all constants")
582+
@skip_if_suppress_immortalization()
583+
def test_interned_constants(self):
584+
# compile separately to avoid compile time de-duping
585+
586+
globals = {}
587+
exec(textwrap.dedent("""
588+
def func1():
589+
return (0.0, (1, 2, "hello"))
590+
"""), globals)
591+
592+
exec(textwrap.dedent("""
593+
def func2():
594+
return (0.0, (1, 2, "hello"))
595+
"""), globals)
596+
597+
self.assertTrue(globals["func1"]() is globals["func2"]())
598+
578599

579600
class CodeWeakRefTest(unittest.TestCase):
580601

Lib/test/test_ctypes/test_internals.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def test_ints(self):
2828
self.assertEqual(ci._objects, None)
2929

3030
def test_c_char_p(self):
31-
s = b"Hello, World"
31+
s = "Hello, World".encode("ascii")
3232
refcnt = sys.getrefcount(s)
3333
cs = c_char_p(s)
3434
self.assertEqual(refcnt + 1, sys.getrefcount(s))

Lib/test/test_ctypes/test_python_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def test_PyLong_Long(self):
4747

4848
@support.refcount_test
4949
def test_PyObj_FromPtr(self):
50-
s = "abc def ghi jkl"
50+
s = object()
5151
ref = sys.getrefcount(s)
5252
# id(python-object) is the address
5353
pyobj = _ctypes.PyObj_FromPtr(id(s))

Lib/test/test_memoryio.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ def test_sizeof(self):
801801

802802
def _test_cow_mutation(self, mutation):
803803
# Common code for all BytesIO copy-on-write mutation tests.
804-
imm = b' ' * 1024
804+
imm = (' ' * 1024).encode("ascii")
805805
old_rc = sys.getrefcount(imm)
806806
memio = self.ioclass(imm)
807807
self.assertEqual(sys.getrefcount(imm), old_rc + 1)

Modules/_testinternalcapi.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,6 +1985,17 @@ set_immortalize_deferred(PyObject *self, PyObject *value)
19851985
#endif
19861986
}
19871987

1988+
static PyObject *
1989+
get_immortalize_deferred(PyObject *self, PyObject *Py_UNUSED(ignored))
1990+
{
1991+
#ifdef Py_GIL_DISABLED
1992+
PyInterpreterState *interp = PyInterpreterState_Get();
1993+
return PyBool_FromLong(interp->gc.immortalize.enable_on_thread_created);
1994+
#else
1995+
Py_RETURN_FALSE;
1996+
#endif
1997+
}
1998+
19881999
static PyObject *
19892000
has_inline_values(PyObject *self, PyObject *obj)
19902001
{
@@ -2081,6 +2092,7 @@ static PyMethodDef module_functions[] = {
20812092
{"py_thread_id", get_py_thread_id, METH_NOARGS},
20822093
#endif
20832094
{"set_immortalize_deferred", set_immortalize_deferred, METH_VARARGS},
2095+
{"get_immortalize_deferred", get_immortalize_deferred, METH_NOARGS},
20842096
#ifdef _Py_TIER2
20852097
{"uop_symbols_test", _Py_uop_symbols_test, METH_NOARGS},
20862098
#endif

0 commit comments

Comments
 (0)
0