8000 GH-133231: Add JIT utilities in `sys._jit` by brandtbucher · Pull Request #133233 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

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

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 17 commits into from
May 5, 2025
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
58 changes: 58 additions & 0 deletions Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions Doc/using/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +1282 to +1288
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should backport this, should it be split out into a different PR for ease?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just backport it separately, since I need the ref for this PR's changes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth using miss-islington for the backport here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8000

I'll just do it manually in a bit.


Debug-mode variables
~~~~~~~~~~~~~~~~~~~~

Expand Down
40 changes: 4 additions & 36 deletions Lib/test/libregrtest/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 67E6 None

if tier2 == 1: # =yes
if PYTHON_JIT == False:
jit = 'JIT=off'
else:
jit = 'JIT'
elif tier2 == 3: # =yes-off
if PYTHON_JIT:
jit = 'JIT'
if sys._jit.is_available():
if sys._jit.is_enabled():
build.append("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')
Expand Down
10 changes: 3 additions & 7 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2648,13 +2648,9 @@ def exceeds_recursion_limit():

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({
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ def test_getitem_with_error(self):
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.
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -1336,7 +1336,7 @@ def test_loop_quicken(self):
# 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)

Expand Down
58 changes: 58 additions & 0 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -2196,6 +2196,64 @@ def test_remote_exec_in_process_without_debug_fails_xoption(self):
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__":
Expand Down
Original file line number Diff line number Diff line change
@@ -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`.
8 changes: 0 additions & 8 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand Down Expand Up @@ -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},
Expand Down
86 changes: 85 additions & 1 deletion Python/clinic/sysmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading
0