summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark Shannon <mark@hotpy.org>2024-12-13 11:00:00 (GMT)
committerGitHub <noreply@github.com>2024-12-13 11:00:00 (GMT)
commite62e1ca4553dbcf9d7f89be24bebcbd9213f9ae5 (patch)
tree96bdcc3ab96408b5970ab657ded4387430c83f58
parent5fc6bb2754a25157575efc0b37da78c629fea46e (diff)
downloadcpython-e62e1ca4553dbcf9d7f89be24bebcbd9213f9ae5.zip
cpython-e62e1ca4553dbcf9d7f89be24bebcbd9213f9ae5.tar.gz
cpython-e62e1ca4553dbcf9d7f89be24bebcbd9213f9ae5.tar.bz2
GH-126833: Dumps graphviz representation of executor graph. (GH-126880)
-rw-r--r--Include/internal/pycore_global_objects_fini_generated.h1
-rw-r--r--Include/internal/pycore_global_strings.h1
-rw-r--r--Include/internal/pycore_optimizer.h5
-rw-r--r--Include/internal/pycore_runtime_init_generated.h1
-rw-r--r--Include/internal/pycore_unicodeobject_generated.h4
-rw-r--r--Python/ceval.c1
-rw-r--r--Python/clinic/sysmodule.c.h58
-rw-r--r--Python/optimizer.c136
-rw-r--r--Python/sysmodule.c25
9 files changed, 230 insertions, 2 deletions
diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h
index c12e242..90214a3 100644
--- a/Include/internal/pycore_global_objects_fini_generated.h
+++ b/Include/internal/pycore_global_objects_fini_generated.h
@@ -1129,6 +1129,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(origin));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(out_fd));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(outgoing));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(outpath));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(overlapped));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(owner));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pages));
diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h
index dfd9f2b..97a75d0 100644
--- a/Include/internal/pycore_global_strings.h
+++ b/Include/internal/pycore_global_strings.h
@@ -618,6 +618,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(origin)
STRUCT_FOR_ID(out_fd)
STRUCT_FOR_ID(outgoing)
+ STRUCT_FOR_ID(outpath)
STRUCT_FOR_ID(overlapped)
STRUCT_FOR_ID(owner)
STRUCT_FOR_ID(pages)
diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h
index 6d70b42..bc7cfcd 100644
--- a/Include/internal/pycore_optimizer.h
+++ b/Include/internal/pycore_optimizer.h
@@ -60,6 +60,9 @@ typedef struct {
};
uint64_t operand0; // A cache entry
uint64_t operand1;
+#ifdef Py_STATS
+ uint64_t execution_count;
+#endif
} _PyUOpInstruction;
typedef struct {
@@ -285,6 +288,8 @@ static inline int is_terminator(const _PyUOpInstruction *uop)
);
}
+PyAPI_FUNC(int) _PyDumpExecutors(FILE *out);
+
#ifdef __cplusplus
}
#endif
diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h
index b631382..4f928cc 100644
--- a/Include/internal/pycore_runtime_init_generated.h
+++ b/Include/internal/pycore_runtime_init_generated.h
@@ -1127,6 +1127,7 @@ extern "C" {
INIT_ID(origin), \
INIT_ID(out_fd), \
INIT_ID(outgoing), \
+ INIT_ID(outpath), \
INIT_ID(overlapped), \
INIT_ID(owner), \
INIT_ID(pages), \
diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h
index 24cec3a..5b78d03 100644
--- a/Include/internal/pycore_unicodeobject_generated.h
+++ b/Include/internal/pycore_unicodeobject_generated.h
@@ -2268,6 +2268,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(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);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
diff --git a/Python/ceval.c b/Python/ceval.c
index 5eda033..fd891d7 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -1095,6 +1095,7 @@ tier2_dispatch:
UOP_PAIR_INC(uopcode, lastuop);
#ifdef Py_STATS
trace_uop_execution_counter++;
+ ((_PyUOpInstruction *)next_uop)[-1].execution_count++;
#endif
switch (uopcode) {
diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h
index 86c42ce..cfcbd55 100644
--- a/Python/clinic/sysmodule.c.h
+++ b/Python/clinic/sysmodule.c.h
@@ -1481,6 +1481,62 @@ sys_is_stack_trampoline_active(PyObject *module, PyObject *Py_UNUSED(ignored))
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__,
"_getframemodulename($module, /, depth=0)\n"
"--\n"
@@ -1668,4 +1724,4 @@ exit:
#ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
#define SYS_GETANDROIDAPILEVEL_METHODDEF
#endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
-/*[clinic end generated code: output=6d4f6cd20419b675 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=568b0a0069dc43e8 input=a9049054013a1b77]*/
diff --git a/Python/optimizer.c b/Python/optimizer.c
index 6a23221..6a4d20f 100644
--- a/Python/optimizer.c
+++ b/Python/optimizer.c
@@ -1,6 +1,7 @@
+#include "Python.h"
+
#ifdef _Py_TIER2
-#include "Python.h"
#include "opcode.h"
#include "pycore_interp.h"
#include "pycore_backoff.h"
@@ -474,6 +475,9 @@ add_to_trace(
trace[trace_length].target = target;
trace[trace_length].oparg = oparg;
trace[trace_length].operand0 = operand;
+#ifdef Py_STATS
+ trace[trace_length].execution_count = 0;
+#endif
return trace_length + 1;
}
@@ -983,6 +987,9 @@ static void make_exit(_PyUOpInstruction *inst, int opcode, int target)
inst->operand0 = 0;
inst->format = UOP_FORMAT_TARGET;
inst->target = target;
+#ifdef Py_STATS
+ inst->execution_count = 0;
+#endif
}
/* Convert implicit exits, errors and deopts
@@ -1709,4 +1716,131 @@ error:
_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 */
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index 6df297f..d6719f9 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -2344,6 +2344,30 @@ sys_is_stack_trampoline_active_impl(PyObject *module)
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]
sys._getframemodulename
@@ -2603,6 +2627,7 @@ static PyMethodDef sys_methods[] = {
#endif
SYS__GET_CPU_COUNT_CONFIG_METHODDEF
SYS__IS_GIL_ENABLED_METHODDEF
+ SYS__DUMP_TRACELETS_METHODDEF
{NULL, NULL} // sentinel
};