Skip to content

Faster dispatch #3

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

Closed
wants to merge 2 commits into from
Closed
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
5 changes: 4 additions & 1 deletion Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -979,9 +979,12 @@ Objects/setobject.o: $(srcdir)/Objects/stringlib/eq.h
regen-opcode-targets:
# Regenerate Python/opcode_targets.h from Lib/opcode.py
# using Python/makeopcodetargets.py
$(PYTHON_FOR_REGEN) $(srcdir)/Python/makeopcodetargets.py \
$(PYTHON_FOR_REGEN) $(srcdir)/Python/makeopcodetargets.py --targets \
$(srcdir)/Python/opcode_targets.h.new
$(UPDATE_FILE) $(srcdir)/Python/opcode_targets.h $(srcdir)/Python/opcode_targets.h.new
$(PYTHON_FOR_REGEN) $(srcdir)/Python/makeopcodetargets.py --unknowns \
$(srcdir)/Python/unknown_opcodes.h.new
$(UPDATE_FILE) $(srcdir)/Python/unknown_opcodes.h $(srcdir)/Python/unknown_opcodes.h.new

Python/ceval.o: $(srcdir)/Python/opcode_targets.h $(srcdir)/Python/ceval_gil.h \
$(srcdir)/Python/condvar.h
Expand Down
95 changes: 38 additions & 57 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ static PyObject * do_call_core(
#ifdef LLTRACE
static int lltrace;
static int prtrace(PyThreadState *, PyObject *, const char *);
#define LLTRACE_ON lltrace
#else
#define LLTRACE_ON 0
#endif
static int call_trace(Py_tracefunc, PyObject *,
PyThreadState *, PyFrameObject *,
Expand Down Expand Up @@ -1292,34 +1295,21 @@ eval_frame_handle_pending(PyThreadState *tstate)
#define TARGET(op) \
op: \
TARGET_##op

#ifdef LLTRACE
#define DISPATCH() \
{ \
if (!lltrace && !_Py_TracingPossible(ceval2) && !PyDTrace_LINE_ENABLED()) { \
f->f_lasti = INSTR_OFFSET(); \
NEXTOPARG(); \
goto *opcode_targets[opcode]; \
} \
goto fast_next_opcode; \
}
#define DISPATCH_GOTO goto *opcode_targets[opcode];
#else
#define TARGET(op) op
#define DISPATCH_GOTO goto dispatch_opcode;
#endif

#define DISPATCH() \
{ \
if (!_Py_TracingPossible(ceval2) && !PyDTrace_LINE_ENABLED()) { \
f->f_lasti = INSTR_OFFSET(); \
NEXTOPARG(); \
goto *opcode_targets[opcode]; \
if (LLTRACE_ON || _Py_TracingPossible(ceval2) || PyDTrace_LINE_ENABLED()) { \
goto tracing_dispatch; \
} \
goto fast_next_opcode; \
f->f_lasti = INSTR_OFFSET(); \
NEXTOPARG(); \
DISPATCH_GOTO \
}
#endif

#else
#define TARGET(op) op
#define DISPATCH() goto fast_next_opcode

#endif

#define CHECK_EVAL_BREAKER() \
if (_Py_atomic_load_relaxed(eval_breaker)) { \
Expand Down Expand Up @@ -1598,22 +1588,14 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
#endif
PyObject **stack_pointer; /* Next free slot in value stack */
const _Py_CODEUNIT *next_instr;
int opcode; /* Current opcode */
uint8_t opcode; /* Current opcode */
int oparg; /* Current opcode argument, if any */
PyObject **fastlocals, **freevars;
PyObject *retval = NULL; /* Return value */
struct _ceval_state * const ceval2 = &tstate->interp->ceval;
_Py_atomic_int * const eval_breaker = &ceval2->eval_breaker;
PyCodeObject *co;

/* when tracing we set things up so that

not (instr_lb <= current_bytecode_offset < instr_ub)

is true when the line being executed has changed. The
initial values are such as to make this false the first
time it is tested. */

const _Py_CODEUNIT *first_instr;
PyObject *names;
PyObject *consts;
Expand All @@ -1628,7 +1610,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
}

PyTraceInfo trace_info;
/* Mark trace_info as initialized */
/* Mark trace_info as uninitialized */
trace_info.code = NULL;

/* push frame */
Expand Down Expand Up @@ -1766,10 +1748,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)

if (_Py_atomic_load_relaxed(eval_breaker)) {
opcode = _Py_OPCODE(*next_instr);
if (opcode == SETUP_FINALLY ||
opcode == SETUP_WITH ||
opcode == BEFORE_ASYNC_WITH ||
opcode == YIELD_FROM) {
if (opcode != SETUP_FINALLY &&
opcode != SETUP_WITH &&
opcode != BEFORE_ASYNC_WITH &&
opcode != YIELD_FROM) {
/* Few cases where we skip running signal handlers and other
pending calls:
- If we're about to enter the 'with:'. It will prevent
Expand All @@ -1786,16 +1768,15 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
running the signal handler and raising KeyboardInterrupt
(see bpo-30039).
*/
goto fast_next_opcode;
}

if (eval_frame_handle_pending(tstate) != 0) {
goto error;
}
if (eval_frame_handle_pending(tstate) != 0) {
goto error;
}
}
}

fast_next_opcode:
tracing_dispatch:
f->f_lasti = INSTR_OFFSET();
NEXTOPARG();

if (PyDTrace_LINE_ENABLED())
maybe_dtrace_line(f, &trace_info);
Expand All @@ -1813,18 +1794,16 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
tstate->c_traceobj,
tstate, f,
&trace_info);
if (err) {
/* trace function raised an exception */
goto error;
}
/* Reload possibly changed frame fields */
JUMPTO(f->f_lasti);
stack_pointer = f->f_valuestack+f->f_stackdepth;
f->f_stackdepth = -1;
if (err)
/* trace function raised an exception */
goto error;
NEXTOPARG();
}

/* Extract opcode and argument */

NEXTOPARG();
dispatch_opcode:
#ifdef DYNAMIC_EXECUTION_PROFILE
#ifdef DXPAIRS
Expand Down Expand Up @@ -1853,7 +1832,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)

/* BEWARE!
It is essential that any operation that fails must goto error
and that all operation that succeed call [FAST_]DISPATCH() ! */
and that all operation that succeed call DISPATCH() ! */

case TARGET(NOP): {
DISPATCH();
Expand Down Expand Up @@ -4452,15 +4431,18 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
goto dispatch_opcode;
}


#if USE_COMPUTED_GOTOS
_unknown_opcode:
#define UNKNOWN_OPCODE(i) _unknown_opcode_ ## i
#else
#define UNKNOWN_OPCODE(i) case i
#endif
default:
#include "unknown_opcodes.h"

_unknown_opcode:
fprintf(stderr,
"XXX lineno: %d, opcode: %d\n",
PyFrame_GetLineNumber(f),
opcode);
oparg);
_PyErr_SetString(tstate, PyExc_SystemError, "unknown opcode");
goto error;

Expand Down Expand Up @@ -5415,7 +5397,6 @@ unpack_iterable(PyThreadState *tstate, PyObject *v,
return 0;
}


#ifdef LLTRACE
static int
prtrace(PyThreadState *tstate, PyObject *v, const char *str)
Expand Down
48 changes: 35 additions & 13 deletions Python/makeopcodetargets.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,51 @@ def find_module(modname):
return SourceFileLoader(modname, modpath).load_module()


def write_contents(f):
"""Write C code contents to the target file object.
"""
def write_targets(f):
opcode = find_module('opcode')
targets = ['_unknown_opcode'] * 256
targets = [f'_unknown_opcode_{i}' for i in range(256)]
for opname, op in opcode.opmap.items():
targets[op] = "TARGET_%s" % opname
f.write("static void *opcode_targets[256] = {\n")
f.write(",\n".join([" &&%s" % s for s in targets]))
f.write("\n};\n")

def write_unknowns(f):
opcode = find_module('opcode')
unknowns = [ True ] * 256
for opname, op in opcode.opmap.items():
unknowns[op] = False
for i, unknown in enumerate(unknowns):
if unknown:
f.write(f" UNKNOWN_OPCODE({i}):\n")
f.write(f" oparg = {i};\n")
f.write(f" goto _unknown_opcode;\n")
f.write("\n")

def main():
if len(sys.argv) >= 3:
sys.exit("Too many arguments")
if len(sys.argv) == 2:
target = sys.argv[1]
if len(sys.argv) >= 4:
sys.exit(f"Too many arguments: {len(sys.argv)}")
output = None
opt = None
for arg in sys.argv[1:]:
if arg.startswith("--"):
opt = arg
else:
output = arg
if opt == "--targets":
if output is None:
output = "Python/opcode_targets.h"
with open(output, "w") as f:
write_targets(f)
print("Jump table written into %s" % output)
elif opt == "--unknowns":
if output is None:
output = "Python/unknown_opcodes.h"
with open(output, "w") as f:
write_unknowns(f)
print("Unknown opcodes written into %s" % output)
else:
target = "Python/opcode_targets.h"
with open(target, "w") as f:
write_contents(f)
print("Jump table written into %s" % target)

sys.exit("Unknwown option " + opt)

if __name__ == "__main__":
main()
Loading