GH-133231: Add JIT utilities in sys._jit (GH-133233)

This commit is contained in:
Brandt Bucher 2025-05-05 15:25:22 -07:00 committed by GitHub
parent f9b22bb79d
commit b1aa515bd6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 296 additions and 54 deletions

View file

@ -1282,6 +1282,64 @@ always available. Unless explicitly noted otherwise, all variables are read-only
.. versionadded:: 3.5
.. data:: _jit
Utilities for observing just-in-time compilation.
.. impl-detail::
JIT compilation is an *experimental implementation detail* of CPython.
``sys._jit`` is not guaranteed to exist or behave the same way in all
Python implementations, versions, or build configurations.
.. versionadded:: next
.. function:: _jit.is_available()
Return ``True`` if the current Python executable supports JIT compilation,
and ``False`` otherwise. This can be controlled by building CPython with
the ``--experimental-jit`` option on Windows, and the
:option:`--enable-experimental-jit` option on all other platforms.
.. function:: _jit.is_enabled()
Return ``True`` if JIT compilation is enabled for the current Python
process (implies :func:`sys._jit.is_available`), and ``False`` otherwise.
If JIT compilation is available, this can be controlled by setting the
:envvar:`PYTHON_JIT` environment variable to ``0`` (disabled) or ``1``
(enabled) at interpreter startup.
.. function:: _jit.is_active()
Return ``True`` if the topmost Python frame is currently executing JIT
code (implies :func:`sys._jit.is_enabled`), and ``False`` otherwise.
.. note::
This function is intended for testing and debugging the JIT itself.
It should be avoided for any other purpose.
.. note::
Due to the nature of tracing JIT compilers, repeated calls to this
function may give surprising results. For example, branching on its
return value will likely lead to unexpected behavior (if doing so
causes JIT code to be entered or exited):
.. code-block:: pycon
>>> for warmup in range(BIG_NUMBER):
... # This line is "hot", and is eventually JIT-compiled:
... if sys._jit.is_active():
... # This line is "cold", and is run in the interpreter:
... assert sys._jit.is_active()
...
Traceback (most recent call last):
File "<stdin>", line 5, in <module>
assert sys._jit.is_active()
~~~~~~~~~~~~~~~~~~^^
AssertionError
.. data:: last_exc
This variable is not always defined; it is set to the exception instance

View file

@ -1279,6 +1279,14 @@ conflict.
.. versionadded:: 3.14
.. envvar:: PYTHON_JIT
On builds where experimental just-in-time compilation is available, this
variable can force the JIT to be disabled (``0``) or enabled (``1``) at
interpreter startup.
.. versionadded:: 3.13
Debug-mode variables
~~~~~~~~~~~~~~~~~~~~

View file

@ -335,43 +335,11 @@ def get_build_info():
build.append('with_assert')
# --enable-experimental-jit
tier2 = re.search('-D_Py_TIER2=([0-9]+)', cflags)
if tier2:
tier2 = int(tier2.group(1))
if not sys.flags.ignore_environment:
PYTHON_JIT = os.environ.get('PYTHON_JIT', None)
if PYTHON_JIT:
PYTHON_JIT = (PYTHON_JIT != '0')
else:
PYTHON_JIT = None
if tier2 == 1: # =yes
if PYTHON_JIT == False:
jit = 'JIT=off'
if sys._jit.is_available():
if sys._jit.is_enabled():
build.append("JIT")
else:
jit = 'JIT'
elif tier2 == 3: # =yes-off
if PYTHON_JIT:
jit = 'JIT'
else:
jit = 'JIT=off'
elif tier2 == 4: # =interpreter
if PYTHON_JIT == False:
jit = 'JIT-interpreter=off'
else:
jit = 'JIT-interpreter'
elif tier2 == 6: # =interpreter-off (Secret option!)
if PYTHON_JIT:
jit = 'JIT-interpreter'
else:
jit = 'JIT-interpreter=off'
elif '-D_Py_JIT' in cflags:
jit = 'JIT'
else:
jit = None
if jit:
build.append(jit)
build.append("JIT (disabled)")
# --enable-framework=name
framework = sysconfig.get_config_var('PYTHONFRAMEWORK')

View file

@ -2648,13 +2648,9 @@ skip_on_s390x = unittest.skipIf(is_s390x, 'skipped on s390x')
Py_TRACE_REFS = hasattr(sys, 'getobjects')
try:
from _testinternalcapi import jit_enabled
except ImportError:
requires_jit_enabled = requires_jit_disabled = unittest.skip("requires _testinternalcapi")
else:
requires_jit_enabled = unittest.skipUnless(jit_enabled(), "requires JIT enabled")
requires_jit_disabled = unittest.skipIf(jit_enabled(), "requires JIT disabled")
_JIT_ENABLED = sys._jit.is_enabled()
requires_jit_enabled = unittest.skipUnless(_JIT_ENABLED, "requires JIT enabled")
requires_jit_disabled = unittest.skipIf(_JIT_ENABLED, "requires JIT disabled")
_BASE_COPY_SRC_DIR_IGNORED_NAMES = frozenset({

View file

@ -306,7 +306,7 @@ class CAPITest(unittest.TestCase):
CURRENT_THREAD_REGEX +
r' File .*, line 6 in <module>\n'
r'\n'
r'Extension modules: _testcapi, _testinternalcapi \(total: 2\)\n')
r'Extension modules: _testcapi \(total: 1\)\n')
else:
# Python built with NDEBUG macro defined:
# test _Py_CheckFunctionResult() instead.

View file

@ -1336,7 +1336,7 @@ class DisTests(DisTestBase):
# Loop can trigger a quicken where the loop is located
self.code_quicken(loop_test)
got = self.get_disassembly(loop_test, adaptive=True)
jit = import_helper.import_module("_testinternalcapi").jit_enabled()
jit = sys._jit.is_enabled()
expected = dis_loop_test_quickened_code.format("JIT" if jit else "NO_JIT")
self.do_disassembly_compare(got, expected)

View file

@ -2196,6 +2196,64 @@ this is invalid python code
self.assertIn(b"Remote debugging is not enabled", err)
self.assertEqual(out, b"")
class TestSysJIT(unittest.TestCase):
def test_jit_is_available(self):
available = sys._jit.is_available()
script = f"import sys; assert sys._jit.is_available() is {available}"
assert_python_ok("-c", script, PYTHON_JIT="0")
assert_python_ok("-c", script, PYTHON_JIT="1")
def test_jit_is_enabled(self):
available = sys._jit.is_available()
script = "import sys; assert sys._jit.is_enabled() is {enabled}"
assert_python_ok("-c", script.format(enabled=False), PYTHON_JIT="0")
assert_python_ok("-c", script.format(enabled=available), PYTHON_JIT="1")
def test_jit_is_active(self):
available = sys._jit.is_available()
script = textwrap.dedent(
"""
import _testcapi
import _testinternalcapi
import sys
def frame_0_interpreter() -> None:
assert sys._jit.is_active() is False
def frame_1_interpreter() -> None:
assert sys._jit.is_active() is False
frame_0_interpreter()
assert sys._jit.is_active() is False
def frame_2_jit(expected: bool) -> None:
# Inlined into the last loop of frame_3_jit:
assert sys._jit.is_active() is expected
# Insert C frame:
_testcapi.pyobject_vectorcall(frame_1_interpreter, None, None)
assert sys._jit.is_active() is expected
def frame_3_jit() -> None:
# JITs just before the last loop:
for i in range(_testinternalcapi.TIER2_THRESHOLD + 1):
# Careful, doing this in the reverse order breaks tracing:
expected = {enabled} and i == _testinternalcapi.TIER2_THRESHOLD
assert sys._jit.is_active() is expected
frame_2_jit(expected)
assert sys._jit.is_active() is expected
def frame_4_interpreter() -> None:
assert sys._jit.is_active() is False
frame_3_jit()
assert sys._jit.is_active() is False
assert sys._jit.is_active() is False
frame_4_interpreter()
assert sys._jit.is_active() is False
"""
)
assert_python_ok("-c", script.format(enabled=False), PYTHON_JIT="0")
assert_python_ok("-c", script.format(enabled=available), PYTHON_JIT="1")
if __name__ == "__main__":

View file

@ -0,0 +1,3 @@
Add new utilities of observing JIT compilation:
:func:`sys._jit.is_available`, :func:`sys._jit.is_enabled`, and
:func:`sys._jit.is_active`.

View file

@ -1206,13 +1206,6 @@ verify_stateless_code(PyObject *self, PyObject *args, PyObject *kwargs)
Py_RETURN_NONE;
}
static PyObject *
jit_enabled(PyObject *self, PyObject *arg)
{
return PyBool_FromLong(_PyInterpreterState_GET()->jit);
}
#ifdef _Py_TIER2
static PyObject *
@ -2337,7 +2330,6 @@ static PyMethodDef module_functions[] = {
METH_VARARGS | METH_KEYWORDS, NULL},
{"verify_stateless_code", _PyCFunction_CAST(verify_stateless_code),
METH_VARARGS | METH_KEYWORDS, NULL},
{"jit_enabled", jit_enabled, METH_NOARGS, NULL},
#ifdef _Py_TIER2
{"add_executor_dependency", add_executor_dependency, METH_VARARGS, NULL},
{"invalidate_executors", invalidate_executors, METH_O, NULL},

View file

@ -1821,6 +1821,90 @@ exit:
return return_value;
}
PyDoc_STRVAR(_jit_is_available__doc__,
"is_available($module, /)\n"
"--\n"
"\n"
"Return True if the current Python executable supports JIT compilation, and False otherwise.");
#define _JIT_IS_AVAILABLE_METHODDEF \
{"is_available", (PyCFunction)_jit_is_available, METH_NOARGS, _jit_is_available__doc__},
static int
_jit_is_available_impl(PyObject *module);
static PyObject *
_jit_is_available(PyObject *module, PyObject *Py_UNUSED(ignored))
{
PyObject *return_value = NULL;
int _return_value;
_return_value = _jit_is_available_impl(module);
if ((_return_value == -1) && PyErr_Occurred()) {
goto exit;
}
return_value = PyBool_FromLong((long)_return_value);
exit:
return return_value;
}
PyDoc_STRVAR(_jit_is_enabled__doc__,
"is_enabled($module, /)\n"
"--\n"
"\n"
"Return True if JIT compilation is enabled for the current Python process (implies sys._jit.is_available()), and False otherwise.");
#define _JIT_IS_ENABLED_METHODDEF \
{"is_enabled", (PyCFunction)_jit_is_enabled, METH_NOARGS, _jit_is_enabled__doc__},
static int
_jit_is_enabled_impl(PyObject *module);
static PyObject *
_jit_is_enabled(PyObject *module, PyObject *Py_UNUSED(ignored))
{
PyObject *return_value = NULL;
int _return_value;
_return_value = _jit_is_enabled_impl(module);
if ((_return_value == -1) && PyErr_Occurred()) {
goto exit;
}
return_value = PyBool_FromLong((long)_return_value);
exit:
return return_value;
}
PyDoc_STRVAR(_jit_is_active__doc__,
"is_active($module, /)\n"
"--\n"
"\n"
"Return True if the topmost Python frame is currently executing JIT code (implies sys._jit.is_enabled()), and False otherwise.");
#define _JIT_IS_ACTIVE_METHODDEF \
{"is_active", (PyCFunction)_jit_is_active, METH_NOARGS, _jit_is_active__doc__},
static int
_jit_is_active_impl(PyObject *module);
static PyObject *
_jit_is_active(PyObject *module, PyObject *Py_UNUSED(ignored))
{
PyObject *return_value = NULL;
int _return_value;
_return_value = _jit_is_active_impl(module);
if ((_return_value == -1) && PyErr_Occurred()) {
goto exit;
}
return_value = PyBool_FromLong((long)_return_value);
exit:
return return_value;
}
#ifndef SYS_GETWINDOWSVERSION_METHODDEF
#define SYS_GETWINDOWSVERSION_METHODDEF
#endif /* !defined(SYS_GETWINDOWSVERSION_METHODDEF) */
@ -1864,4 +1948,4 @@ exit:
#ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
#define SYS_GETANDROIDAPILEVEL_METHODDEF
#endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
/*[clinic end generated code: output=1aca52cefbeb800f input=a9049054013a1b77]*/
/*[clinic end generated code: output=449d16326e69dcf6 input=a9049054013a1b77]*/

View file

@ -3986,6 +3986,71 @@ error:
PyObject *_Py_CreateMonitoringObject(void);
/*[clinic input]
module _jit
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=10952f74d7bbd972]*/
PyDoc_STRVAR(_jit_doc, "Utilities for observing just-in-time compilation.");
/*[clinic input]
_jit.is_available -> bool
Return True if the current Python executable supports JIT compilation, and False otherwise.
[clinic start generated code]*/
static int
_jit_is_available_impl(PyObject *module)
/*[clinic end generated code: output=6849a9cd2ff4aac9 input=03add84aa8347cf1]*/
{
(void)module;
#ifdef _Py_TIER2
return true;
#else
return false;
#endif
}
/*[clinic input]
_jit.is_enabled -> bool
Return True if JIT compilation is enabled for the current Python process (implies sys._jit.is_available()), and False otherwise.
[clinic start generated code]*/
static int
_jit_is_enabled_impl(PyObject *module)
/*[clinic end generated code: output=55865f8de993fe42 input=02439394da8e873f]*/
{
(void)module;
return _PyInterpreterState_GET()->jit;
}
/*[clinic input]
_jit.is_active -> bool
Return True if the topmost Python frame is currently executing JIT code (implies sys._jit.is_enabled()), and False otherwise.
[clinic start generated code]*/
static int
_jit_is_active_impl(PyObject *module)
/*[clinic end generated code: output=7facca06b10064d4 input=be2fcd8a269d9b72]*/
{
(void)module;
return _PyThreadState_GET()->current_executor != NULL;
}
static PyMethodDef _jit_methods[] = {
_JIT_IS_AVAILABLE_METHODDEF
_JIT_IS_ENABLED_METHODDEF
_JIT_IS_ACTIVE_METHODDEF
{NULL}
};
static struct PyModuleDef _jit_module = {
PyModuleDef_HEAD_INIT,
.m_name = "sys._jit",
.m_doc = _jit_doc,
.m_size = -1,
.m_methods = _jit_methods,
};
/* Create sys module without all attributes.
_PySys_UpdateConfig() should be called later to add remaining attributes. */
PyStatus
@ -4047,6 +4112,16 @@ _PySys_Create(PyThreadState *tstate, PyObject **sysmod_p)
goto error;
}
PyObject *_jit = _PyModule_CreateInitialized(&_jit_module, PYTHON_API_VERSION);
if (_jit == NULL) {
goto error;
}
err = PyDict_SetItemString(sysdict, "_jit", _jit);
Py_DECREF(_jit);
if (err) {
goto error;
}
assert(!_PyErr_Occurred(tstate));
*sysmod_p = sysmod;