Skip to content

Commit

Permalink
GH-126833: Dumps graphviz representation of executor graph. (GH-126880)
Browse files Browse the repository at this point in the history
  • Loading branch information
markshannon authored Dec 13, 2024
1 parent 5fc6bb2 commit e62e1ca
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 2 deletions.
1 change: 1 addition & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions Include/internal/pycore_optimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -285,6 +288,8 @@ static inline int is_terminator(const _PyUOpInstruction *uop)
);
}

PyAPI_FUNC(int) _PyDumpExecutors(FILE *out);

#ifdef __cplusplus
}
#endif
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
UOP_PAIR_INC(uopcode, lastuop);
#ifdef Py_STATS
trace_uop_execution_counter++;
((_PyUOpInstruction *)next_uop)[-1].execution_count++;
#endif

switch (uopcode) {
Expand Down
58 changes: 57 additions & 1 deletion Python/clinic/sysmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

136 changes: 135 additions & 1 deletion Python/optimizer.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "Python.h"

#ifdef _Py_TIER2

#include "Python.h"
#include "opcode.h"
#include "pycore_interp.h"
#include "pycore_backoff.h"
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1709,4 +1716,131 @@ _Py_Executors_InvalidateCold(PyInterpreterState *interp)
_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 */
25 changes: 25 additions & 0 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
};

Expand Down

0 comments on commit e62e1ca

Please sign in to comment.