Skip to content

Commit b1aa515

Browse files
authored
GH-133231: Add JIT utilities in sys._jit (GH-133233)
1 parent f9b22bb commit b1aa515

File tree

11 files changed

+296
-54
lines changed

11 files changed

+296
-54
lines changed

Doc/library/sys.rst

+58
Original file line numberDiff line numberDiff line change
@@ -1282,6 +1282,64 @@ always available. Unless explicitly noted otherwise, all variables are read-only
12821282

12831283
.. versionadded:: 3.5
12841284

1285+
.. data:: _jit
1286+
1287+
Utilities for observing just-in-time compilation.
1288+
1289+
.. impl-detail::
1290+
1291+
JIT compilation is an *experimental implementation detail* of CPython.
1292+
``sys._jit`` is not guaranteed to exist or behave the same way in all
1293+
Python implementations, versions, or build configurations.
1294+
1295+
.. versionadded:: next
1296+
1297+
.. function:: _jit.is_available()
1298+
1299+
Return ``True`` if the current Python executable supports JIT compilation,
1300+
and ``False`` otherwise. This can be controlled by building CPython with
1301+
the ``--experimental-jit`` option on Windows, and the
1302+
:option:`--enable-experimental-jit` option on all other platforms.
1303+
1304+
.. function:: _jit.is_enabled()
1305+
1306+
Return ``True`` if JIT compilation is enabled for the current Python
1307+
process (implies :func:`sys._jit.is_available`), and ``False`` otherwise.
1308+
If JIT compilation is available, this can be controlled by setting the
1309+
:envvar:`PYTHON_JIT` environment variable to ``0`` (disabled) or ``1``
1310+
(enabled) at interpreter startup.
1311+
1312+
.. function:: _jit.is_active()
1313+
1314+
Return ``True`` if the topmost Python frame is currently executing JIT
1315+
code (implies :func:`sys._jit.is_enabled`), and ``False`` otherwise.
1316+
1317+
.. note::
1318+
1319+
This function is intended for testing and debugging the JIT itself.
1320+
It should be avoided for any other purpose.
1321+
1322+
.. note::
1323+
1324+
Due to the nature of tracing JIT compilers, repeated calls to this
1325+
function may give surprising results. For example, branching on its
1326+
return value will likely lead to unexpected behavior (if doing so
1327+
causes JIT code to be entered or exited):
1328+
1329+
.. code-block:: pycon
1330+
1331+
>>> for warmup in range(BIG_NUMBER):
1332+
... # This line is "hot", and is eventually JIT-compiled:
1333+
... if sys._jit.is_active():
1334+
... # This line is "cold", and is run in the interpreter:
1335+
... assert sys._jit.is_active()
1336+
...
1337+
Traceback (most recent call last):
1338+
File "<stdin>", line 5, in <module>
1339+
assert sys._jit.is_active()
1340+
~~~~~~~~~~~~~~~~~~^^
1341+
AssertionError
1342+
12851343
.. data:: last_exc
12861344

12871345
This variable is not always defined; it is set to the exception instance

Doc/using/cmdline.rst

+8
Original file line numberDiff line numberDiff line change
@@ -1279,6 +1279,14 @@ conflict.
12791279

12801280
.. versionadded:: 3.14
12811281

1282+
.. envvar:: PYTHON_JIT
1283+
1284+
On builds where experimental just-in-time compilation is available, this
1285+
variable can force the JIT to be disabled (``0``) or enabled (``1``) at
1286+
interpreter startup.
1287+
1288+
.. versionadded:: 3.13
1289+
12821290
Debug-mode variables
12831291
~~~~~~~~~~~~~~~~~~~~
12841292

Lib/test/libregrtest/utils.py

+4-36
Original file line numberDiff line numberDiff line change
@@ -335,43 +335,11 @@ def get_build_info():
335335
build.append('with_assert')
336336

337337
# --enable-experimental-jit
338-
tier2 = re.search('-D_Py_TIER2=([0-9]+)', cflags)
339-
if tier2:
340-
tier2 = int(tier2.group(1))
341-
342-
if not sys.flags.ignore_environment:
343-
PYTHON_JIT = os.environ.get('PYTHON_JIT', None)
344-
if PYTHON_JIT:
345-
PYTHON_JIT = (PYTHON_JIT != '0')
346-
else:
347-
PYTHON_JIT = None
348-
349-
if tier2 == 1: # =yes
350-
if PYTHON_JIT == False:
351-
jit = 'JIT=off'
352-
else:
353-
jit = 'JIT'
354-
elif tier2 == 3: # =yes-off
355-
if PYTHON_JIT:
356-
jit = 'JIT'
338+
if sys._jit.is_available():
339+
if sys._jit.is_enabled():
340+
build.append("JIT")
357341
else:
358-
jit = 'JIT=off'
359-
elif tier2 == 4: # =interpreter
360-
if PYTHON_JIT == False:
361-
jit = 'JIT-interpreter=off'
362-
else:
363-
jit = 'JIT-interpreter'
364-
elif tier2 == 6: # =interpreter-off (Secret option!)
365-
if PYTHON_JIT:
366-
jit = 'JIT-interpreter'
367-
else:
368-
jit = 'JIT-interpreter=off'
369-
elif '-D_Py_JIT' in cflags:
370-
jit = 'JIT'
371-
else:
372-
jit = None
373-
if jit:
374-
build.append(jit)
342+
build.append("JIT (disabled)")
375343

376344
# --enable-framework=name
377345
framework = sysconfig.get_config_var('PYTHONFRAMEWORK')

Lib/test/support/__init__.py

+3-7
Original file line numberDiff line numberDiff line change
@@ -2648,13 +2648,9 @@ def exceeds_recursion_limit():
26482648

26492649
Py_TRACE_REFS = hasattr(sys, 'getobjects')
26502650

2651-
try:
2652-
from _testinternalcapi import jit_enabled
2653-
except ImportError:
2654-
requires_jit_enabled = requires_jit_disabled = unittest.skip("requires _testinternalcapi")
2655-
else:
2656-
requires_jit_enabled = unittest.skipUnless(jit_enabled(), "requires JIT enabled")
2657-
requires_jit_disabled = unittest.skipIf(jit_enabled(), "requires JIT disabled")
2651+
_JIT_ENABLED = sys._jit.is_enabled()
2652+
requires_jit_enabled = unittest.skipUnless(_JIT_ENABLED, "requires JIT enabled")
2653+
requires_jit_disabled = unittest.skipIf(_JIT_ENABLED, "requires JIT disabled")
26582654

26592655

26602656
_BASE_COPY_SRC_DIR_IGNORED_NAMES = frozenset({

Lib/test/test_capi/test_misc.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ def test_getitem_with_error(self):
306306
CURRENT_THREAD_REGEX +
307307
r' File .*, line 6 in <module>\n'
308308
r'\n'
309-
r'Extension modules: _testcapi, _testinternalcapi \(total: 2\)\n')
309+
r'Extension modules: _testcapi \(total: 1\)\n')
310310
else:
311311
# Python built with NDEBUG macro defined:
312312
# test _Py_CheckFunctionResult() instead.

Lib/test/test_dis.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1336,7 +1336,7 @@ def test_loop_quicken(self):
13361336
# Loop can trigger a quicken where the loop is located
13371337
self.code_quicken(loop_test)
13381338
got = self.get_disassembly(loop_test, adaptive=True)
1339-
jit = import_helper.import_module("_testinternalcapi").jit_enabled()
1339+
jit = sys._jit.is_enabled()
13401340
expected = dis_loop_test_quickened_code.format("JIT" if jit else "NO_JIT")
13411341
self.do_disassembly_compare(got, expected)
13421342

Lib/test/test_sys.py

+58
Original file line numberDiff line numberDiff line change
@@ -2196,6 +2196,64 @@ def test_remote_exec_in_process_without_debug_fails_xoption(self):
21962196
self.assertIn(b"Remote debugging is not enabled", err)
21972197
self.assertEqual(out, b"")
21982198

2199+
class TestSysJIT(unittest.TestCase):
2200+
2201+
def test_jit_is_available(self):
2202+
available = sys._jit.is_available()
2203+
script = f"import sys; assert sys._jit.is_available() is {available}"
2204+
assert_python_ok("-c", script, PYTHON_JIT="0")
2205+
assert_python_ok("-c", script, PYTHON_JIT="1")
2206+
2207+
def test_jit_is_enabled(self):
2208+
available = sys._jit.is_available()
2209+
script = "import sys; assert sys._jit.is_enabled() is {enabled}"
2210+
assert_python_ok("-c", script.format(enabled=False), PYTHON_JIT="0")
2211+
assert_python_ok("-c", script.format(enabled=available), PYTHON_JIT="1")
2212+
2213+
def test_jit_is_active(self):
2214+
available = sys._jit.is_available()
2215+
script = textwrap.dedent(
2216+
"""
2217+
import _testcapi
2218+
import _testinternalcapi
2219+
import sys
2220+
2221+
def frame_0_interpreter() -> None:
2222+
assert sys._jit.is_active() is False
2223+
2224+
def frame_1_interpreter() -> None:
2225+
assert sys._jit.is_active() is False
2226+
frame_0_interpreter()
2227+
assert sys._jit.is_active() is False
2228+
2229+
def frame_2_jit(expected: bool) -> None:
2230+
# Inlined into the last loop of frame_3_jit:
2231+
assert sys._jit.is_active() is expected
2232+
# Insert C frame:
2233+
_testcapi.pyobject_vectorcall(frame_1_interpreter, None, None)
2234+
assert sys._jit.is_active() is expected
2235+
2236+
def frame_3_jit() -> None:
2237+
# JITs just before the last loop:
2238+
for i in range(_testinternalcapi.TIER2_THRESHOLD + 1):
2239+
# Careful, doing this in the reverse order breaks tracing:
2240+
expected = {enabled} and i == _testinternalcapi.TIER2_THRESHOLD
2241+
assert sys._jit.is_active() is expected
2242+
frame_2_jit(expected)
2243+
assert sys._jit.is_active() is expected
2244+
2245+
def frame_4_interpreter() -> None:
2246+
assert sys._jit.is_active() is False
2247+
frame_3_jit()
2248+
assert sys._jit.is_active() is False
2249+
2250+
assert sys._jit.is_active() is False
2251+
frame_4_interpreter()
2252+
assert sys._jit.is_active() is False
2253+
"""
2254+
)
2255+
assert_python_ok("-c", script.format(enabled=False), PYTHON_JIT="0")
2256+
assert_python_ok("-c", script.format(enabled=available), PYTHON_JIT="1")
21992257

22002258

22012259
if __name__ == "__main__":
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add new utilities of observing JIT compilation:
2+
:func:`sys._jit.is_available`, :func:`sys._jit.is_enabled`, and
3+
:func:`sys._jit.is_active`.

Modules/_testinternalcapi.c

-8
Original file line numberDiff line numberDiff line change
@@ -1206,13 +1206,6 @@ verify_stateless_code(PyObject *self, PyObject *args, PyObject *kwargs)
12061206
Py_RETURN_NONE;
12071207
}
12081208

1209-
1210-
static PyObject *
1211-
jit_enabled(PyObject *self, PyObject *arg)
1212-
{
1213-
return PyBool_FromLong(_PyInterpreterState_GET()->jit);
1214-
}
1215-
12161209
#ifdef _Py_TIER2
12171210

12181211
static PyObject *
@@ -2337,7 +2330,6 @@ static PyMethodDef module_functions[] = {
23372330
METH_VARARGS | METH_KEYWORDS, NULL},
23382331
{"verify_stateless_code", _PyCFunction_CAST(verify_stateless_code),
23392332
METH_VARARGS | METH_KEYWORDS, NULL},
2340-
{"jit_enabled", jit_enabled, METH_NOARGS, NULL},
23412333
#ifdef _Py_TIER2
23422334
{"add_executor_dependency", add_executor_dependency, METH_VARARGS, NULL},
23432335
{"invalidate_executors", invalidate_executors, METH_O, NULL},

Python/clinic/sysmodule.c.h

+85-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)