Skip to content

gh-118527: Intern code consts in free-threaded build #118667

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Include/internal/pycore_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif

#include "pycore_lock.h" // PyMutex


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

struct _py_code_state {
PyMutex mutex;
// Interned constants from code objects. Used by the free-threaded build.
struct _Py_hashtable_t *constants;
};

extern PyStatus _PyCode_Init(PyInterpreterState *interp);
extern void _PyCode_Fini(PyInterpreterState *interp);

#define CODE_MAX_WATCHERS 8

Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ struct _is {
struct _Py_long_state long_state;
struct _dtoa_state dtoa;
struct _py_func_state func_state;
struct _py_code_state code_state;

struct _Py_dict_state dict_state;
struct _Py_exc_state exc_state;
Expand Down
3 changes: 3 additions & 0 deletions Include/internal/pycore_setobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ PyAPI_DATA(PyObject *) _PySet_Dummy;

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

// Clears the set without acquiring locks. Used by _PyCode_Fini.
extern void _PySet_ClearInternal(PySetObject *so);

#ifdef __cplusplus
}
#endif
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,15 @@ def suppress_immortalization(suppress=True):
finally:
_testinternalcapi.set_immortalize_deferred(*old_values)

def skip_if_suppress_immortalization():
try:
import _testinternalcapi
except ImportError:
return
return unittest.skipUnless(_testinternalcapi.get_immortalize_deferred(),
"requires immortalization of deferred objects")


MS_WINDOWS = (sys.platform == 'win32')

# Is not actually used in tests, but is kept for compatibility.
Expand Down
23 changes: 22 additions & 1 deletion Lib/test/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@
from test.support import (cpython_only,
check_impl_detail, requires_debug_ranges,
gc_collect, Py_GIL_DISABLED,
suppress_immortalization)
suppress_immortalization,
skip_if_suppress_immortalization)
from test.support.script_helper import assert_python_ok
from test.support import threading_helper, import_helper
from test.support.bytecode_helper import instructions_with_positions
Expand Down Expand Up @@ -570,11 +571,31 @@ def f(a='str_value'):
self.assertIsInterned(f())

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

@cpython_only
@unittest.skipUnless(Py_GIL_DISABLED, "does not intern all constants")
@skip_if_suppress_immortalization()
def test_interned_constants(self):
# compile separately to avoid compile time de-duping

globals = {}
exec(textwrap.dedent("""
def func1():
return (0.0, (1, 2, "hello"))
"""), globals)

exec(textwrap.dedent("""
def func2():
return (0.0, (1, 2, "hello"))
"""), globals)

self.assertTrue(globals["func1"]() is globals["func2"]())


class CodeWeakRefTest(unittest.TestCase):

Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_ctypes/test_internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def test_ints(self):
self.assertEqual(ci._objects, None)

def test_c_char_p(self):
s = b"Hello, World"
s = "Hello, World".encode("ascii")
refcnt = sys.getrefcount(s)
cs = c_char_p(s)
self.assertEqual(refcnt + 1, sys.getrefcount(s))
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_ctypes/test_python_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def test_PyLong_Long(self):

@support.refcount_test
def test_PyObj_FromPtr(self):
s = "abc def ghi jkl"
s = object()
ref = sys.getrefcount(s)
# id(python-object) is the address
pyobj = _ctypes.PyObj_FromPtr(id(s))
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_memoryio.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,7 +801,7 @@ def test_sizeof(self):

def _test_cow_mutation(self, mutation):
# Common code for all BytesIO copy-on-write mutation tests.
imm = b' ' * 1024
imm = (' ' * 1024).encode("ascii")
old_rc = sys.getrefcount(imm)
memio = self.ioclass(imm)
self.assertEqual(sys.getrefcount(imm), old_rc + 1)
Expand Down
12 changes: 12 additions & 0 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1985,6 +1985,17 @@ set_immortalize_deferred(PyObject *self, PyObject *value)
#endif
}

static PyObject *
get_immortalize_deferred(PyObject *self, PyObject *Py_UNUSED(ignored))
{
#ifdef Py_GIL_DISABLED
PyInterpreterState *interp = PyInterpreterState_Get();
return PyBool_FromLong(interp->gc.immortalize.enable_on_thread_created);
#else
Py_RETURN_FALSE;
#endif
}

static PyObject *
has_inline_values(PyObject *self, PyObject *obj)
{
Expand Down Expand Up @@ -2081,6 +2092,7 @@ static PyMethodDef module_functions[] = {
{"py_thread_id", get_py_thread_id, METH_NOARGS},
#endif
{"set_immortalize_deferred", set_immortalize_deferred, METH_VARARGS},
{"get_immortalize_deferred", get_immortalize_deferred, METH_NOARGS},
#ifdef _Py_TIER2
{"uop_symbols_test", _Py_uop_symbols_test, METH_NOARGS},
#endif
Expand Down
Loading