.. 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
.. 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
~~~~~~~~~~~~~~~~~~~~
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'
- 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')
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({
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.
# 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)
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__":
--- /dev/null
+Add new utilities of observing JIT compilation:
+:func:`sys._jit.is_available`, :func:`sys._jit.is_enabled`, and
+:func:`sys._jit.is_active`.
Py_RETURN_NONE;
}
-
-static PyObject *
-jit_enabled(PyObject *self, PyObject *arg)
-{
- return PyBool_FromLong(_PyInterpreterState_GET()->jit);
-}
-
#ifdef _Py_TIER2
static PyObject *
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},
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) */
#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]*/
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
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;