gh-118335: Configure Tier 2 interpreter at build time (#118339)

The code for Tier 2 is now only compiled when configured
with `--enable-experimental-jit[=yes|interpreter]`.

We drop support for `PYTHON_UOPS` and -`Xuops`,
but you can disable the interpreter or JIT
at runtime by setting `PYTHON_JIT=0`.
You can also build it without enabling it by default
using `--enable-experimental-jit=yes-off`;
enable with `PYTHON_JIT=1`.

On Windows, the `build.bat` script supports
`--experimental-jit`, `--experimental-jit-off`,
`--experimental-interpreter`.

In the C code, `_Py_JIT` is defined as before
when the JIT is enabled; the new variable
`_Py_TIER2` is defined when the JIT *or* the
interpreter is enabled. It is actually a bitmask:
1: JIT; 2: default-off; 4: interpreter.
This commit is contained in:
Guido van Rossum 2024-04-30 18:26:34 -07:00 committed by GitHub
parent 9c468e2c5d
commit 7d83f7bcc4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 181 additions and 42 deletions

View file

@ -2363,6 +2363,7 @@ dummy_func(
CHECK_EVAL_BREAKER();
assert(oparg <= INSTR_OFFSET());
JUMPBY(-oparg);
#ifdef _Py_TIER2
#if ENABLE_SPECIALIZATION
_Py_BackoffCounter counter = this_instr[1].counter;
if (backoff_counter_triggers(counter) && this_instr->op.code == JUMP_BACKWARD) {
@ -2388,6 +2389,7 @@ dummy_func(
ADVANCE_ADAPTIVE_COUNTER(this_instr[1].counter);
}
#endif /* ENABLE_SPECIALIZATION */
#endif /* _Py_TIER2 */
}
pseudo(JUMP) = {
@ -2401,6 +2403,7 @@ dummy_func(
};
tier1 inst(ENTER_EXECUTOR, (--)) {
#ifdef _Py_TIER2
int prevoparg = oparg;
CHECK_EVAL_BREAKER();
if (this_instr->op.code != ENTER_EXECUTOR ||
@ -2418,6 +2421,9 @@ dummy_func(
tstate->previous_executor = Py_None;
Py_INCREF(executor);
GOTO_TIER_TWO(executor);
#else
Py_FatalError("ENTER_EXECUTOR is not supported in this build");
#endif /* _Py_TIER2 */
}
replaced op(_POP_JUMP_IF_FALSE, (cond -- )) {

View file

@ -755,7 +755,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
_Py_CODEUNIT *next_instr;
PyObject **stack_pointer;
#ifndef _Py_JIT
#if defined(_Py_TIER2) && !defined(_Py_JIT)
/* Tier 2 interpreter state */
_PyExecutorObject *current_executor = NULL;
const _PyUOpInstruction *next_uop = NULL;
@ -959,6 +959,7 @@ resume_with_error:
goto error;
#ifdef _Py_TIER2
// Tier 2 is also here!
enter_tier_two:
@ -1113,6 +1114,8 @@ exit_to_trace:
#endif // _Py_JIT
#endif // _Py_TIER2
}
#if defined(__GNUC__)

View file

@ -2492,6 +2492,7 @@
(void)this_instr;
next_instr += 1;
INSTRUCTION_STATS(ENTER_EXECUTOR);
#ifdef _Py_TIER2
int prevoparg = oparg;
CHECK_EVAL_BREAKER();
if (this_instr->op.code != ENTER_EXECUTOR ||
@ -2508,6 +2509,9 @@
tstate->previous_executor = Py_None;
Py_INCREF(executor);
GOTO_TIER_TWO(executor);
#else
Py_FatalError("ENTER_EXECUTOR is not supported in this build");
#endif /* _Py_TIER2 */
DISPATCH();
}
@ -3432,6 +3436,7 @@
CHECK_EVAL_BREAKER();
assert(oparg <= INSTR_OFFSET());
JUMPBY(-oparg);
#ifdef _Py_TIER2
#if ENABLE_SPECIALIZATION
_Py_BackoffCounter counter = this_instr[1].counter;
if (backoff_counter_triggers(counter) && this_instr->op.code == JUMP_BACKWARD) {
@ -3457,6 +3462,7 @@
ADVANCE_ADAPTIVE_COUNTER(this_instr[1].counter);
}
#endif /* ENABLE_SPECIALIZATION */
#endif /* _Py_TIER2 */
DISPATCH();
}

View file

@ -1705,10 +1705,12 @@ instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
);
return 0;
}
#ifdef _Py_TIER2
if (code->co_executors != NULL) {
_PyCode_Clear_Executors(code);
}
_Py_Executors_InvalidateDependency(interp, code, 1);
#endif
int code_len = (int)Py_SIZE(code);
/* Exit early to avoid creating instrumentation
* data for potential statically allocated code
@ -1946,7 +1948,9 @@ _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events)
goto done;
}
set_global_version(tstate, new_version);
#ifdef _Py_TIER2
_Py_Executors_InvalidateAll(interp, 1);
#endif
res = instrument_all_executing_code_objects(interp);
done:
_PyEval_StartTheWorld(interp);
@ -1986,7 +1990,9 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent
code->_co_instrumentation_version -= MONITORING_VERSION_INCREMENT;
}
#ifdef _Py_TIER2
_Py_Executors_InvalidateDependency(interp, code, 1);
#endif
res = instrument_lock_held(code, interp);

View file

@ -1,3 +1,5 @@
#ifdef _Py_TIER2
#include "Python.h"
#include "opcode.h"
#include "pycore_interp.h"
@ -1622,3 +1624,5 @@ _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation)
}
}
}
#endif /* _Py_TIER2 */

View file

@ -1,3 +1,5 @@
#ifdef _Py_TIER2
/*
* This file contains the support code for CPython's uops optimizer.
* It also performs some simple optimizations.
@ -603,3 +605,5 @@ _Py_uop_analyze_and_optimize(
OPT_STAT_INC(optimizer_successes);
return length;
}
#endif /* _Py_TIER2 */

View file

@ -1,3 +1,4 @@
#ifdef _Py_TIER2
#include "Python.h"
@ -506,3 +507,5 @@ fail:
Py_XDECREF(val_43);
return NULL;
}
#endif /* _Py_TIER2 */

View file

@ -624,9 +624,11 @@ static int
builtins_dict_watcher(PyDict_WatchEvent event, PyObject *dict, PyObject *key, PyObject *new_value)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
#ifdef _Py_TIER2
if (interp->rare_events.builtin_dict < _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS) {
_Py_Executors_InvalidateAll(interp, 1);
}
#endif
RARE_EVENT_INTERP_INC(interp, builtin_dict);
return 0;
}
@ -1272,30 +1274,30 @@ init_interp_main(PyThreadState *tstate)
}
// Turn on experimental tier 2 (uops-based) optimizer
// This is also needed when the JIT is enabled
#ifdef _Py_TIER2
if (is_main_interp) {
#ifndef _Py_JIT
// No JIT, maybe use the tier two interpreter:
char *envvar = Py_GETENV("PYTHON_UOPS");
int enabled = envvar != NULL && *envvar > '0';
if (_Py_get_xoption(&config->xoptions, L"uops") != NULL) {
enabled = 1;
int enabled = 1;
#if _Py_TIER2 & 2
enabled = 0;
#endif
char *env = Py_GETENV("PYTHON_JIT");
if (env && *env != '\0') {
// PYTHON_JIT=0|1 overrides the default
enabled = *env != '0';
}
if (enabled) {
#else
// Always enable tier two for JIT builds (ignoring the environment
// variable and command-line option above):
if (true) {
#endif
PyObject *opt = PyUnstable_Optimizer_NewUOpOptimizer();
if (opt == NULL) {
return _PyStatus_ERR("can't initialize optimizer");
}
if (PyUnstable_SetOptimizer((_PyOptimizerObject *)opt)) {
return _PyStatus_ERR("can't initialize optimizer");
return _PyStatus_ERR("can't install optimizer");
}
Py_DECREF(opt);
}
}
#endif
if (!is_main_interp) {
// The main interpreter is handled in Py_Main(), for now.
@ -1655,10 +1657,12 @@ finalize_modules(PyThreadState *tstate)
{
PyInterpreterState *interp = tstate->interp;
#ifdef _Py_TIER2
// Invalidate all executors and turn off tier 2 optimizer
_Py_Executors_InvalidateAll(interp, 0);
_PyOptimizerObject *old = _Py_SetOptimizer(interp, NULL);
Py_XDECREF(old);
#endif
// Stop watching __builtin__ modifications
PyDict_Unwatch(0, interp->builtins);

View file

@ -653,8 +653,10 @@ init_interpreter(PyInterpreterState *interp,
}
interp->sys_profile_initialized = false;
interp->sys_trace_initialized = false;
#ifdef _Py_TIER2
(void)_Py_SetOptimizer(interp, NULL);
interp->executor_list_head = NULL;
#endif
if (interp != &runtime->_main_interpreter) {
/* Fix the self-referential, statically initialized fields. */
interp->dtoa = (struct _dtoa_state)_dtoa_state_INIT(interp);
@ -806,9 +808,11 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
tstate->_status.cleared = 0;
}
#ifdef _Py_TIER2
_PyOptimizerObject *old = _Py_SetOptimizer(interp, NULL);
assert(old != NULL);
Py_DECREF(old);
#endif
/* It is possible that any of the objects below have a finalizer
that runs Python code or otherwise relies on a thread state
@ -2821,9 +2825,11 @@ _PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp,
if (eval_frame == interp->eval_frame) {
return;
}
#ifdef _Py_TIER2
if (eval_frame != NULL) {
_Py_Executors_InvalidateAll(interp, 1);
}
#endif
RARE_EVENT_INC(set_eval_frame_func);
interp->eval_frame = eval_frame;
}

View file

@ -2165,8 +2165,10 @@ static PyObject *
sys__clear_internal_caches_impl(PyObject *module)
/*[clinic end generated code: output=0ee128670a4966d6 input=253e741ca744f6e8]*/
{
#ifdef _Py_TIER2
PyInterpreterState *interp = _PyInterpreterState_GET();
_Py_Executors_InvalidateAll(interp, 0);
#endif
PyType_ClearCache();
Py_RETURN_NONE;
}