mirror of
https://github.com/python/cpython.git
synced 2025-08-31 22:18:28 +00:00
GH-126833: Dumps graphviz representation of executor graph. (GH-126880)
This commit is contained in:
parent
5fc6bb2754
commit
e62e1ca455
9 changed files with 230 additions and 2 deletions
|
@ -1129,6 +1129,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(origin));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(origin));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(out_fd));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(out_fd));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(outgoing));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(outgoing));
|
||||||
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(outpath));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(overlapped));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(overlapped));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(owner));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(owner));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pages));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pages));
|
||||||
|
|
|
@ -618,6 +618,7 @@ struct _Py_global_strings {
|
||||||
STRUCT_FOR_ID(origin)
|
STRUCT_FOR_ID(origin)
|
||||||
STRUCT_FOR_ID(out_fd)
|
STRUCT_FOR_ID(out_fd)
|
||||||
STRUCT_FOR_ID(outgoing)
|
STRUCT_FOR_ID(outgoing)
|
||||||
|
STRUCT_FOR_ID(outpath)
|
||||||
STRUCT_FOR_ID(overlapped)
|
STRUCT_FOR_ID(overlapped)
|
||||||
STRUCT_FOR_ID(owner)
|
STRUCT_FOR_ID(owner)
|
||||||
STRUCT_FOR_ID(pages)
|
STRUCT_FOR_ID(pages)
|
||||||
|
|
|
@ -60,6 +60,9 @@ typedef struct {
|
||||||
};
|
};
|
||||||
uint64_t operand0; // A cache entry
|
uint64_t operand0; // A cache entry
|
||||||
uint64_t operand1;
|
uint64_t operand1;
|
||||||
|
#ifdef Py_STATS
|
||||||
|
uint64_t execution_count;
|
||||||
|
#endif
|
||||||
} _PyUOpInstruction;
|
} _PyUOpInstruction;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -285,6 +288,8 @@ static inline int is_terminator(const _PyUOpInstruction *uop)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyAPI_FUNC(int) _PyDumpExecutors(FILE *out);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
1
Include/internal/pycore_runtime_init_generated.h
generated
1
Include/internal/pycore_runtime_init_generated.h
generated
|
@ -1127,6 +1127,7 @@ extern "C" {
|
||||||
INIT_ID(origin), \
|
INIT_ID(origin), \
|
||||||
INIT_ID(out_fd), \
|
INIT_ID(out_fd), \
|
||||||
INIT_ID(outgoing), \
|
INIT_ID(outgoing), \
|
||||||
|
INIT_ID(outpath), \
|
||||||
INIT_ID(overlapped), \
|
INIT_ID(overlapped), \
|
||||||
INIT_ID(owner), \
|
INIT_ID(owner), \
|
||||||
INIT_ID(pages), \
|
INIT_ID(pages), \
|
||||||
|
|
|
@ -2268,6 +2268,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
|
||||||
_PyUnicode_InternStatic(interp, &string);
|
_PyUnicode_InternStatic(interp, &string);
|
||||||
assert(_PyUnicode_CheckConsistency(string, 1));
|
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||||
assert(PyUnicode_GET_LENGTH(string) != 1);
|
assert(PyUnicode_GET_LENGTH(string) != 1);
|
||||||
|
string = &_Py_ID(outpath);
|
||||||
|
_PyUnicode_InternStatic(interp, &string);
|
||||||
|
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||||
|
assert(PyUnicode_GET_LENGTH(string) != 1);
|
||||||
string = &_Py_ID(overlapped);
|
string = &_Py_ID(overlapped);
|
||||||
_PyUnicode_InternStatic(interp, &string);
|
_PyUnicode_InternStatic(interp, &string);
|
||||||
assert(_PyUnicode_CheckConsistency(string, 1));
|
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||||
|
|
|
@ -1095,6 +1095,7 @@ tier2_dispatch:
|
||||||
UOP_PAIR_INC(uopcode, lastuop);
|
UOP_PAIR_INC(uopcode, lastuop);
|
||||||
#ifdef Py_STATS
|
#ifdef Py_STATS
|
||||||
trace_uop_execution_counter++;
|
trace_uop_execution_counter++;
|
||||||
|
((_PyUOpInstruction *)next_uop)[-1].execution_count++;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
switch (uopcode) {
|
switch (uopcode) {
|
||||||
|
|
58
Python/clinic/sysmodule.c.h
generated
58
Python/clinic/sysmodule.c.h
generated
|
@ -1481,6 +1481,62 @@ sys_is_stack_trampoline_active(PyObject *module, PyObject *Py_UNUSED(ignored))
|
||||||
return sys_is_stack_trampoline_active_impl(module);
|
return sys_is_stack_trampoline_active_impl(module);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(sys__dump_tracelets__doc__,
|
||||||
|
"_dump_tracelets($module, /, outpath)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Dump the graph of tracelets in graphviz format");
|
||||||
|
|
||||||
|
#define SYS__DUMP_TRACELETS_METHODDEF \
|
||||||
|
{"_dump_tracelets", _PyCFunction_CAST(sys__dump_tracelets), METH_FASTCALL|METH_KEYWORDS, sys__dump_tracelets__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
sys__dump_tracelets_impl(PyObject *module, PyObject *outpath);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
sys__dump_tracelets(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||||
|
|
||||||
|
#define NUM_KEYWORDS 1
|
||||||
|
static struct {
|
||||||
|
PyGC_Head _this_is_not_used;
|
||||||
|
PyObject_VAR_HEAD
|
||||||
|
PyObject *ob_item[NUM_KEYWORDS];
|
||||||
|
} _kwtuple = {
|
||||||
|
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
|
||||||
|
.ob_item = { &_Py_ID(outpath), },
|
||||||
|
};
|
||||||
|
#undef NUM_KEYWORDS
|
||||||
|
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
|
||||||
|
|
||||||
|
#else // !Py_BUILD_CORE
|
||||||
|
# define KWTUPLE NULL
|
||||||
|
#endif // !Py_BUILD_CORE
|
||||||
|
|
||||||
|
static const char * const _keywords[] = {"outpath", NULL};
|
||||||
|
static _PyArg_Parser _parser = {
|
||||||
|
.keywords = _keywords,
|
||||||
|
.fname = "_dump_tracelets",
|
||||||
|
.kwtuple = KWTUPLE,
|
||||||
|
};
|
||||||
|
#undef KWTUPLE
|
||||||
|
PyObject *argsbuf[1];
|
||||||
|
PyObject *outpath;
|
||||||
|
|
||||||
|
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
|
||||||
|
/*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
|
||||||
|
if (!args) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
outpath = args[0];
|
||||||
|
return_value = sys__dump_tracelets_impl(module, outpath);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(sys__getframemodulename__doc__,
|
PyDoc_STRVAR(sys__getframemodulename__doc__,
|
||||||
"_getframemodulename($module, /, depth=0)\n"
|
"_getframemodulename($module, /, depth=0)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
@ -1668,4 +1724,4 @@ exit:
|
||||||
#ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
|
#ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
|
||||||
#define SYS_GETANDROIDAPILEVEL_METHODDEF
|
#define SYS_GETANDROIDAPILEVEL_METHODDEF
|
||||||
#endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
|
#endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
|
||||||
/*[clinic end generated code: output=6d4f6cd20419b675 input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=568b0a0069dc43e8 input=a9049054013a1b77]*/
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
#include "Python.h"
|
||||||
|
|
||||||
#ifdef _Py_TIER2
|
#ifdef _Py_TIER2
|
||||||
|
|
||||||
#include "Python.h"
|
|
||||||
#include "opcode.h"
|
#include "opcode.h"
|
||||||
#include "pycore_interp.h"
|
#include "pycore_interp.h"
|
||||||
#include "pycore_backoff.h"
|
#include "pycore_backoff.h"
|
||||||
|
@ -474,6 +475,9 @@ add_to_trace(
|
||||||
trace[trace_length].target = target;
|
trace[trace_length].target = target;
|
||||||
trace[trace_length].oparg = oparg;
|
trace[trace_length].oparg = oparg;
|
||||||
trace[trace_length].operand0 = operand;
|
trace[trace_length].operand0 = operand;
|
||||||
|
#ifdef Py_STATS
|
||||||
|
trace[trace_length].execution_count = 0;
|
||||||
|
#endif
|
||||||
return trace_length + 1;
|
return trace_length + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -983,6 +987,9 @@ static void make_exit(_PyUOpInstruction *inst, int opcode, int target)
|
||||||
inst->operand0 = 0;
|
inst->operand0 = 0;
|
||||||
inst->format = UOP_FORMAT_TARGET;
|
inst->format = UOP_FORMAT_TARGET;
|
||||||
inst->target = target;
|
inst->target = target;
|
||||||
|
#ifdef Py_STATS
|
||||||
|
inst->execution_count = 0;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Convert implicit exits, errors and deopts
|
/* Convert implicit exits, errors and deopts
|
||||||
|
@ -1709,4 +1716,131 @@ error:
|
||||||
_Py_Executors_InvalidateAll(interp, 0);
|
_Py_Executors_InvalidateAll(interp, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
write_str(PyObject *str, FILE *out)
|
||||||
|
{
|
||||||
|
// Encode the Unicode object to the specified encoding
|
||||||
|
PyObject *encoded_obj = PyUnicode_AsEncodedString(str, "utf8", "strict");
|
||||||
|
if (encoded_obj == NULL) {
|
||||||
|
PyErr_Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const char *encoded_str = PyBytes_AsString(encoded_obj);
|
||||||
|
Py_ssize_t encoded_size = PyBytes_Size(encoded_obj);
|
||||||
|
fwrite(encoded_str, 1, encoded_size, out);
|
||||||
|
Py_DECREF(encoded_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
find_line_number(PyCodeObject *code, _PyExecutorObject *executor)
|
||||||
|
{
|
||||||
|
int code_len = (int)Py_SIZE(code);
|
||||||
|
for (int i = 0; i < code_len; i++) {
|
||||||
|
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
|
||||||
|
int opcode = instr->op.code;
|
||||||
|
if (opcode == ENTER_EXECUTOR) {
|
||||||
|
_PyExecutorObject *exec = code->co_executors->executors[instr->op.arg];
|
||||||
|
if (exec == executor) {
|
||||||
|
return PyCode_Addr2Line(code, i*2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += _PyOpcode_Caches[_Py_GetBaseCodeUnit(code, i).op.code];
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Writes the node and outgoing edges for a single tracelet in graphviz format.
|
||||||
|
* Each tracelet is presented as a table of the uops it contains.
|
||||||
|
* If Py_STATS is enabled, execution counts are included.
|
||||||
|
*
|
||||||
|
* https://graphviz.readthedocs.io/en/stable/manual.html
|
||||||
|
* https://graphviz.org/gallery/
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
executor_to_gv(_PyExecutorObject *executor, FILE *out)
|
||||||
|
{
|
||||||
|
PyCodeObject *code = executor->vm_data.code;
|
||||||
|
fprintf(out, "executor_%p [\n", executor);
|
||||||
|
fprintf(out, " shape = none\n");
|
||||||
|
|
||||||
|
/* Write the HTML table for the uops */
|
||||||
|
fprintf(out, " label = <<table border=\"0\" cellspacing=\"0\">\n");
|
||||||
|
fprintf(out, " <tr><td port=\"start\" border=\"1\" ><b>Executor</b></td></tr>\n");
|
||||||
|
if (code == NULL) {
|
||||||
|
fprintf(out, " <tr><td border=\"1\" >No code object</td></tr>\n");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fprintf(out, " <tr><td border=\"1\" >");
|
||||||
|
write_str(code->co_qualname, out);
|
||||||
|
int line = find_line_number(code, executor);
|
||||||
|
fprintf(out, ": %d</td></tr>\n", line);
|
||||||
|
}
|
||||||
|
for (uint32_t i = 0; i < executor->code_size; i++) {
|
||||||
|
/* Write row for uop.
|
||||||
|
* The `port` is a marker so that outgoing edges can
|
||||||
|
* be placed correctly. If a row is marked `port=17`,
|
||||||
|
* then the outgoing edge is `{EXEC_NAME}:17 -> {TARGET}`
|
||||||
|
* https://graphviz.readthedocs.io/en/stable/manual.html#node-ports-compass
|
||||||
|
*/
|
||||||
|
_PyUOpInstruction const *inst = &executor->trace[i];
|
||||||
|
const char *opname = _PyOpcode_uop_name[inst->opcode];
|
||||||
|
#ifdef Py_STATS
|
||||||
|
fprintf(out, " <tr><td port=\"i%d\" border=\"1\" >%s -- %" PRIu64 "</td></tr>\n", i, opname, inst->execution_count);
|
||||||
|
#else
|
||||||
|
fprintf(out, " <tr><td port=\"i%d\" border=\"1\" >%s</td></tr>\n", i, opname);
|
||||||
|
#endif
|
||||||
|
if (inst->opcode == _EXIT_TRACE || inst->opcode == _JUMP_TO_TOP) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(out, " </table>>\n");
|
||||||
|
fprintf(out, "]\n\n");
|
||||||
|
|
||||||
|
/* Write all the outgoing edges */
|
||||||
|
for (uint32_t i = 0; i < executor->code_size; i++) {
|
||||||
|
_PyUOpInstruction const *inst = &executor->trace[i];
|
||||||
|
uint16_t flags = _PyUop_Flags[inst->opcode];
|
||||||
|
_PyExitData *exit = NULL;
|
||||||
|
if (inst->opcode == _EXIT_TRACE) {
|
||||||
|
exit = (_PyExitData *)inst->operand0;
|
||||||
|
}
|
||||||
|
else if (flags & HAS_EXIT_FLAG) {
|
||||||
|
assert(inst->format == UOP_FORMAT_JUMP);
|
||||||
|
_PyUOpInstruction const *exit_inst = &executor->trace[inst->jump_target];
|
||||||
|
assert(exit_inst->opcode == _EXIT_TRACE);
|
||||||
|
exit = (_PyExitData *)exit_inst->operand0;
|
||||||
|
}
|
||||||
|
if (exit != NULL && exit->executor != NULL) {
|
||||||
|
fprintf(out, "executor_%p:i%d -> executor_%p:start\n", executor, i, exit->executor);
|
||||||
|
}
|
||||||
|
if (inst->opcode == _EXIT_TRACE || inst->opcode == _JUMP_TO_TOP) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write the graph of all the live tracelets in graphviz format. */
|
||||||
|
int
|
||||||
|
_PyDumpExecutors(FILE *out)
|
||||||
|
{
|
||||||
|
fprintf(out, "digraph ideal {\n\n");
|
||||||
|
fprintf(out, " rankdir = \"LR\"\n\n");
|
||||||
|
PyInterpreterState *interp = PyInterpreterState_Get();
|
||||||
|
for (_PyExecutorObject *exec = interp->executor_list_head; exec != NULL;) {
|
||||||
|
executor_to_gv(exec, out);
|
||||||
|
exec = exec->vm_data.links.next;
|
||||||
|
}
|
||||||
|
fprintf(out, "}\n\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyDumpExecutors(FILE *out)
|
||||||
|
{
|
||||||
|
PyErr_SetString(PyExc_NotImplementedError, "No JIT available");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* _Py_TIER2 */
|
#endif /* _Py_TIER2 */
|
||||||
|
|
|
@ -2344,6 +2344,30 @@ sys_is_stack_trampoline_active_impl(PyObject *module)
|
||||||
Py_RETURN_FALSE;
|
Py_RETURN_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
sys._dump_tracelets
|
||||||
|
|
||||||
|
outpath: object
|
||||||
|
|
||||||
|
Dump the graph of tracelets in graphviz format
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
sys__dump_tracelets_impl(PyObject *module, PyObject *outpath)
|
||||||
|
/*[clinic end generated code: output=a7fe265e2bc3b674 input=5bff6880cd28ffd1]*/
|
||||||
|
{
|
||||||
|
FILE *out = _Py_fopen_obj(outpath, "wb");
|
||||||
|
if (out == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
int err = _PyDumpExecutors(out);
|
||||||
|
fclose(out);
|
||||||
|
if (err) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
sys._getframemodulename
|
sys._getframemodulename
|
||||||
|
@ -2603,6 +2627,7 @@ static PyMethodDef sys_methods[] = {
|
||||||
#endif
|
#endif
|
||||||
SYS__GET_CPU_COUNT_CONFIG_METHODDEF
|
SYS__GET_CPU_COUNT_CONFIG_METHODDEF
|
||||||
SYS__IS_GIL_ENABLED_METHODDEF
|
SYS__IS_GIL_ENABLED_METHODDEF
|
||||||
|
SYS__DUMP_TRACELETS_METHODDEF
|
||||||
{NULL, NULL} // sentinel
|
{NULL, NULL} // sentinel
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue