gh-111520: Integrate the Tier 2 interpreter in the Tier 1 interpreter (#111428)

- There is no longer a separate Python/executor.c file.
- Conventions in Python/bytecodes.c are slightly different -- don't use `goto error`,
  you must use `GOTO_ERROR(error)` (same for others like `unused_local_error`).
- The `TIER_ONE` and `TIER_TWO` symbols are only valid in the generated (.c.h) files.
- In Lib/test/support/__init__.py, `Py_C_RECURSION_LIMIT` is imported from `_testcapi`.
- On Windows, in debug mode, stack allocation grows from 8MiB to 12MiB.
- **Beware!** This changes the env vars to enable uops and their debugging
  to `PYTHON_UOPS` and `PYTHON_LLTRACE`.
This commit is contained in:
Guido van Rossum 2023-11-01 13:13:02 -07:00 committed by GitHub
parent 5d6db168b9
commit 7e135a48d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 509 additions and 487 deletions

View file

@ -202,15 +202,15 @@ maybe_lltrace_resume_frame(_PyInterpreterFrame *frame, _PyInterpreterFrame *skip
if (r < 0) {
return -1;
}
int lltrace = r;
int lltrace = r * 5; // Levels 1-4 only trace uops
if (!lltrace) {
// When tracing executed uops, also trace bytecode
char *uop_debug = Py_GETENV("PYTHONUOPSDEBUG");
if (uop_debug != NULL && *uop_debug >= '0') {
lltrace = (*uop_debug - '0') >= 5; // TODO: Parse an int and all that
// Can also be controlled by environment variable
char *python_lltrace = Py_GETENV("PYTHON_LLTRACE");
if (python_lltrace != NULL && *python_lltrace >= '0') {
lltrace = *python_lltrace - '0'; // TODO: Parse an int and all that
}
}
if (lltrace) {
if (lltrace >= 5) {
lltrace_resume_frame(frame);
}
return lltrace;
@ -611,10 +611,8 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
return _PyEval_EvalFrame(tstate, f->f_frame, throwflag);
}
#define TIER_ONE 1
#include "ceval_macros.h"
int _Py_CheckRecursiveCallPy(
PyThreadState *tstate)
{
@ -680,9 +678,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
#ifdef Py_STATS
int lastopcode = 0;
#endif
// opcode is an 8-bit value to improve the code generated by MSVC
// for the big switch below (in combination with the EXTRA_CASES macro).
uint8_t opcode; /* Current opcode */
int opcode; /* Current opcode */
int oparg; /* Current opcode argument, if any */
#ifdef LLTRACE
int lltrace = 0;
@ -730,6 +726,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
goto resume_with_error;
}
/* State shared between Tier 1 and Tier 2 interpreter */
_PyUOpExecutorObject *current_executor = NULL;
/* Local "register" variables.
* These are cached values from the frame and code object. */
@ -766,7 +765,9 @@ resume_frame:
/* Start instructions */
#if !USE_COMPUTED_GOTOS
dispatch_opcode:
switch (opcode)
// Cast to an 8-bit value to improve the code generated by MSVC
// (in combination with the EXTRA_CASES macro).
switch ((uint8_t)opcode)
#endif
{
@ -914,7 +915,7 @@ exception_unwind:
}
/* Resume normal execution */
#ifdef LLTRACE
if (lltrace) {
if (lltrace >= 5) {
lltrace_resume_frame(frame);
}
#endif
@ -943,6 +944,135 @@ resume_with_error:
stack_pointer = _PyFrame_GetStackPointer(frame);
goto error;
// The Tier 2 interpreter is also here!
enter_tier_two:
#undef LOAD_IP
#define LOAD_IP(UNUSED) (void)0
#undef GOTO_ERROR
#define GOTO_ERROR(LABEL) goto LABEL ## _tier_two
#undef DEOPT_IF
#define DEOPT_IF(COND, INSTNAME) \
if ((COND)) { \
goto deoptimize;\
}
#ifdef Py_STATS
// Disable these macros that apply to Tier 1 stats when we are in Tier 2
#undef STAT_INC
#define STAT_INC(opname, name) ((void)0)
#undef STAT_DEC
#define STAT_DEC(opname, name) ((void)0)
#undef CALL_STAT_INC
#define CALL_STAT_INC(name) ((void)0)
#endif
#undef ENABLE_SPECIALIZATION
#define ENABLE_SPECIALIZATION 0
#ifdef Py_DEBUG
#define DPRINTF(level, ...) \
if (lltrace >= (level)) { printf(__VA_ARGS__); }
#else
#define DPRINTF(level, ...)
#endif
OPT_STAT_INC(traces_executed);
_PyUOpInstruction *next_uop = current_executor->trace;
#ifdef Py_DEBUG
uint64_t operand; // Used by several DPRINTF() calls
#endif
#ifdef Py_STATS
uint64_t trace_uop_execution_counter = 0;
#endif
for (;;) {
opcode = next_uop->opcode;
oparg = next_uop->oparg;
#ifdef Py_DEBUG
operand = next_uop->operand;
#endif
DPRINTF(3,
"%4d: uop %s, oparg %d, operand %" PRIu64 ", stack_level %d\n",
(int)(next_uop - current_executor->trace),
opcode < 256 ? _PyOpcode_OpName[opcode] : _PyOpcode_uop_name[opcode],
oparg,
operand,
(int)(stack_pointer - _PyFrame_Stackbase(frame)));
next_uop++;
OPT_STAT_INC(uops_executed);
#ifdef Py_STATS
trace_uop_execution_counter++;
#endif
switch (opcode) {
#include "executor_cases.c.h"
default:
#ifdef Py_DEBUG
{
fprintf(stderr, "Unknown uop %d, oparg %d, operand %" PRIu64 "\n",
opcode, oparg, operand);
Py_FatalError("Unknown uop");
}
#else
Py_UNREACHABLE();
#endif
}
}
// Jump here from ERROR_IF(..., unbound_local_error)
unbound_local_error_tier_two:
_PyEval_FormatExcCheckArg(tstate, PyExc_UnboundLocalError,
UNBOUNDLOCAL_ERROR_MSG,
PyTuple_GetItem(_PyFrame_GetCode(frame)->co_localsplusnames, oparg)
);
goto error_tier_two;
// JUMP to any of these from ERROR_IF(..., error)
pop_4_error_tier_two:
STACK_SHRINK(1);
pop_3_error_tier_two:
STACK_SHRINK(1);
pop_2_error_tier_two:
STACK_SHRINK(1);
pop_1_error_tier_two:
STACK_SHRINK(1);
error_tier_two:
DPRINTF(2, "Error: [Opcode %d, operand %" PRIu64 "]\n", opcode, operand);
OPT_HIST(trace_uop_execution_counter, trace_run_length_hist);
frame->return_offset = 0; // Don't leave this random
_PyFrame_SetStackPointer(frame, stack_pointer);
Py_DECREF(current_executor);
goto resume_with_error;
// Jump here from DEOPT_IF()
deoptimize:
// On DEOPT_IF we just repeat the last instruction.
// This presumes nothing was popped from the stack (nor pushed).
DPRINTF(2, "DEOPT: [Opcode %d, operand %" PRIu64 "]\n", opcode, operand);
OPT_HIST(trace_uop_execution_counter, trace_run_length_hist);
frame->return_offset = 0; // Dispatch to frame->instr_ptr
_PyFrame_SetStackPointer(frame, stack_pointer);
Py_DECREF(current_executor);
// Fall through
// Jump here from ENTER_EXECUTOR
enter_tier_one:
next_instr = frame->instr_ptr;
goto resume_frame;
// Jump here from _EXIT_TRACE
exit_trace:
_PyFrame_SetStackPointer(frame, stack_pointer);
Py_DECREF(current_executor);
OPT_HIST(trace_uop_execution_counter, trace_run_length_hist);
goto enter_tier_one;
}
#if defined(__GNUC__)
# pragma GCC diagnostic pop