From 033b16e39234542e7f6ca10ea0d69312c2e7df5e Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 14 Nov 2024 17:06:31 +0000 Subject: [PATCH 01/12] First attempt --- Include/internal/pycore_optimizer.h | 2 ++ Modules/_testinternalcapi.c | 16 +++++++++++ Python/optimizer.c | 43 +++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 6d70b42f708854..c2d505904d25ce 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -285,6 +285,8 @@ static inline int is_terminator(const _PyUOpInstruction *uop) ); } +PyAPI_FUNC(void) _PyDumpExecutors(FILE *out); + #ifdef __cplusplus } #endif diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index b02f794d27d5bd..5dc085b3f408b1 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2076,6 +2076,21 @@ has_deferred_refcount(PyObject *self, PyObject *op) return PyBool_FromLong(_PyObject_HasDeferredRefcount(op)); } +static PyObject * +dump_executors(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ +# ifdef MS_WINDOWS + const char *dirname = "c:\\temp\\py_stats\\"; +# else + const char *dirname = "/tmp/py_stats/"; +# endif + char buf[64]; + sprintf(buf, "%s%s.gv", dirname, "executor_dump"); + FILE *out = fopen(buf, "w"); + _PyDumpExecutors(out); + Py_RETURN_NONE; +} + static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, @@ -2174,6 +2189,7 @@ static PyMethodDef module_functions[] = { {"get_static_builtin_types", get_static_builtin_types, METH_NOARGS}, {"identify_type_slot_wrappers", identify_type_slot_wrappers, METH_NOARGS}, {"has_deferred_refcount", has_deferred_refcount, METH_O}, + {"dump_executors", dump_executors, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/optimizer.c b/Python/optimizer.c index bc2ecc098b0e15..f8d64f8e5435bb 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1699,4 +1699,47 @@ _Py_Executors_InvalidateCold(PyInterpreterState *interp) _Py_Executors_InvalidateAll(interp, 0); } + +static void +dump_executor(_PyExecutorObject *executor, FILE *out) +{ + fprintf(out, "executor_%p [\n", executor); + fprintf(out, " shape = none\n"); + fprintf(out, " label = <\n"); + fprintf(out, " \n"); + for (int i = 0; i < executor->code_size; i++) { + _PyUOpInstruction *inst = &executor->trace[i]; + const char *opname = _PyOpcode_uop_name[inst->opcode]; + fprintf(out, " \n", opname); + } + for (int i = 0; i < executor->exit_count; i++) { + _PyExitData *exit = &executor->exits[i]; + int temp = exit->temperature.value_and_backoff >> 4; + fprintf(out, " \n", i, temp); + } + + fprintf(out, " label =
Executor
%s
EXIT: temp %d
>\n"); + fprintf(out, "]\n\n"); + for (int i = 0; i < executor->exit_count; i++) { + _PyExitData *exit = &executor->exits[i]; + if (exit->executor != NULL) { + fprintf(out, "executor_%p:%d -> executor_%p:start\n", executor, i, exit->executor); + } + } +} + +void +_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;) { + dump_executor(exec, out); + exec = exec->vm_data.links.next; + } + fprintf(out, "}\n\n"); + fclose(out); +} + #endif /* _Py_TIER2 */ From f43d4236036eca299c31fdd72393c83608ea5113 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 15 Nov 2024 08:57:43 +0000 Subject: [PATCH 02/12] Add lots of arrows --- Python/optimizer.c | 55 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index f8d64f8e5435bb..3aaeef839cc5db 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1699,31 +1699,80 @@ _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; +} static void dump_executor(_PyExecutorObject *executor, FILE *out) { + PyCodeObject *code = executor->vm_data.code; fprintf(out, "executor_%p [\n", executor); fprintf(out, " shape = none\n"); fprintf(out, " label = <\n"); fprintf(out, " \n"); + if (code == NULL) { + fprintf(out, " \n"); + } + else { + fprintf(out, " \n"); + int line = find_line_number(code, executor); + fprintf(out, " \n", line); + } for (int i = 0; i < executor->code_size; i++) { _PyUOpInstruction *inst = &executor->trace[i]; const char *opname = _PyOpcode_uop_name[inst->opcode]; - fprintf(out, " \n", opname); + fprintf(out, " \n", i, opname); } for (int i = 0; i < executor->exit_count; i++) { _PyExitData *exit = &executor->exits[i]; int temp = exit->temperature.value_and_backoff >> 4; - fprintf(out, " \n", i, temp); + fprintf(out, " \n", i, temp); } fprintf(out, " label =
Executor
No code object
"); + write_str(code->co_qualname, out); + fprintf(out, "
line: %d
%s
%s
EXIT: temp %d
EXIT: temp %d
>\n"); fprintf(out, "]\n\n"); + for (int i = 0; i < executor->code_size; i++) { + _PyUOpInstruction *inst = &executor->trace[i]; + if (inst->format == UOP_FORMAT_JUMP) { + int exit = inst->jump_target; + fprintf(out, "executor_%p:i%d -> executor_%p:i%d\n", executor, i, executor, exit); + } + } + for (int i = 0; i < executor->exit_count; i++) { _PyExitData *exit = &executor->exits[i]; if (exit->executor != NULL) { - fprintf(out, "executor_%p:%d -> executor_%p:start\n", executor, i, exit->executor); + fprintf(out, "executor_%p:e%d -> executor_%p:start\n", executor, i, exit->executor); } } } From 61a0404987f9ef42ca865a19fd98be939369350c Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 15 Nov 2024 18:14:23 +0000 Subject: [PATCH 03/12] Draw arrow from instruction to target. Add inctruction counts --- Include/internal/pycore_optimizer.h | 3 ++ Python/ceval.c | 1 + Python/optimizer.c | 43 +++++++++++++++-------------- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index c2d505904d25ce..516a86d0db7a03 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 { diff --git a/Python/ceval.c b/Python/ceval.c index 9a608f06966688..1503b1c58d7e55 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1098,6 +1098,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) { diff --git a/Python/optimizer.c b/Python/optimizer.c index 3aaeef839cc5db..37176af048e7a2 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -464,6 +464,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; } @@ -973,6 +976,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 @@ -1748,31 +1754,28 @@ dump_executor(_PyExecutorObject *executor, FILE *out) int line = find_line_number(code, executor); fprintf(out, " line: %d\n", line); } - for (int i = 0; i < executor->code_size; i++) { - _PyUOpInstruction *inst = &executor->trace[i]; + for (uint32_t i = 0; i < executor->code_size; i++) { + _PyUOpInstruction const *inst = &executor->trace[i]; const char *opname = _PyOpcode_uop_name[inst->opcode]; +#ifdef Py_STATS + fprintf(out, " %s -- %" PRIu64 "\n", i, opname, inst->execution_count); +#else fprintf(out, " %s\n", i, opname); +#endif } - for (int i = 0; i < executor->exit_count; i++) { - _PyExitData *exit = &executor->exits[i]; - int temp = exit->temperature.value_and_backoff >> 4; - fprintf(out, " EXIT: temp %d\n", i, temp); - } - fprintf(out, " label = >\n"); fprintf(out, "]\n\n"); - for (int i = 0; i < executor->code_size; i++) { - _PyUOpInstruction *inst = &executor->trace[i]; - if (inst->format == UOP_FORMAT_JUMP) { - int exit = inst->jump_target; - fprintf(out, "executor_%p:i%d -> executor_%p:i%d\n", executor, i, executor, exit); - } - } - - for (int i = 0; i < executor->exit_count; i++) { - _PyExitData *exit = &executor->exits[i]; - if (exit->executor != NULL) { - fprintf(out, "executor_%p:e%d -> executor_%p:start\n", executor, i, exit->executor); + for (uint32_t i = 0; i < executor->code_size; i++) { + _PyUOpInstruction const *inst = &executor->trace[i]; + uint16_t flags = _PyUop_Flags[inst->opcode]; + 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); + _PyExitData *exit = (_PyExitData *)exit_inst->operand0; + if (exit->executor != NULL) { + fprintf(out, "executor_%p:i%d -> executor_%p:start\n", executor, i, exit->executor); + } } } } From 3ed885042e4ed0b71a220b845cb8e2fd88b3f679 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 15 Nov 2024 19:02:40 +0000 Subject: [PATCH 04/12] Include final _EXIT_TRACE, but not trailing instructions --- Python/optimizer.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index 37176af048e7a2..e775d179e83909 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1750,9 +1750,8 @@ dump_executor(_PyExecutorObject *executor, FILE *out) else { fprintf(out, " "); write_str(code->co_qualname, out); - fprintf(out, "\n"); int line = find_line_number(code, executor); - fprintf(out, " line: %d\n", line); + fprintf(out, ": %d\n", line); } for (uint32_t i = 0; i < executor->code_size; i++) { _PyUOpInstruction const *inst = &executor->trace[i]; @@ -1762,20 +1761,30 @@ dump_executor(_PyExecutorObject *executor, FILE *out) #else fprintf(out, " %s\n", i, opname); #endif + if (inst->opcode == _EXIT_TRACE || inst->opcode == _JUMP_TO_TOP) { + break; + } } fprintf(out, " label = >\n"); fprintf(out, "]\n\n"); for (uint32_t i = 0; i < executor->code_size; i++) { _PyUOpInstruction const *inst = &executor->trace[i]; uint16_t flags = _PyUop_Flags[inst->opcode]; - if (flags & HAS_EXIT_FLAG) { + _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); - _PyExitData *exit = (_PyExitData *)exit_inst->operand0; - if (exit->executor != NULL) { - fprintf(out, "executor_%p:i%d -> executor_%p:start\n", executor, i, exit->executor); - } + 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; } } } From 0797e655a689975c15c99e054fcfe299689257e8 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 18 Nov 2024 14:00:11 +0000 Subject: [PATCH 05/12] Dump to specified filepath --- Modules/_testinternalcapi.c | 19 ++++++++----------- Python/optimizer.c | 1 - 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 5dc085b3f408b1..1fa08cb630af41 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2077,17 +2077,14 @@ has_deferred_refcount(PyObject *self, PyObject *op) } static PyObject * -dump_executors(PyObject *self, PyObject *Py_UNUSED(ignored)) -{ -# ifdef MS_WINDOWS - const char *dirname = "c:\\temp\\py_stats\\"; -# else - const char *dirname = "/tmp/py_stats/"; -# endif - char buf[64]; - sprintf(buf, "%s%s.gv", dirname, "executor_dump"); - FILE *out = fopen(buf, "w"); +dump_executors(PyObject *self, PyObject *filepath) +{ + FILE *out = _Py_fopen_obj(filepath, "wb"); + if (out == NULL) { + return NULL; + } _PyDumpExecutors(out); + fclose(out); Py_RETURN_NONE; } @@ -2189,7 +2186,7 @@ static PyMethodDef module_functions[] = { {"get_static_builtin_types", get_static_builtin_types, METH_NOARGS}, {"identify_type_slot_wrappers", identify_type_slot_wrappers, METH_NOARGS}, {"has_deferred_refcount", has_deferred_refcount, METH_O}, - {"dump_executors", dump_executors, METH_NOARGS}, + {"dump_executors", dump_executors, METH_O}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/optimizer.c b/Python/optimizer.c index e775d179e83909..3dd7ec44e2c32a 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1800,7 +1800,6 @@ _PyDumpExecutors(FILE *out) exec = exec->vm_data.links.next; } fprintf(out, "}\n\n"); - fclose(out); } #endif /* _Py_TIER2 */ From 5efd035b6b3c84f84806ba86362011a371e29c64 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 18 Nov 2024 21:45:47 +0000 Subject: [PATCH 06/12] Move to sys module --- .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 4 ++ Modules/_testinternalcapi.c | 14 ----- Python/clinic/sysmodule.c.h | 58 ++++++++++++++++++- Python/sysmodule.c | 22 +++++++ 7 files changed, 86 insertions(+), 15 deletions(-) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index e4f0138e17edfa..abf8100efc7cc8 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1128,6 +1128,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 e70f11e2a26cd5..84e5812673cdbf 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -617,6 +617,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_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 5d404c8fd91ca6..f4b5dc604b89b0 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1126,6 +1126,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 d0bc8d7186c053..d575b82a95cb49 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -2264,6 +2264,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/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 1fa08cb630af41..4e99b3296234a8 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2076,19 +2076,6 @@ has_deferred_refcount(PyObject *self, PyObject *op) return PyBool_FromLong(_PyObject_HasDeferredRefcount(op)); } -static PyObject * -dump_executors(PyObject *self, PyObject *filepath) -{ - FILE *out = _Py_fopen_obj(filepath, "wb"); - if (out == NULL) { - return NULL; - } - _PyDumpExecutors(out); - fclose(out); - Py_RETURN_NONE; -} - - static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, @@ -2186,7 +2173,6 @@ static PyMethodDef module_functions[] = { {"get_static_builtin_types", get_static_builtin_types, METH_NOARGS}, {"identify_type_slot_wrappers", identify_type_slot_wrappers, METH_NOARGS}, {"has_deferred_refcount", has_deferred_refcount, METH_O}, - {"dump_executors", dump_executors, METH_O}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index 86c42ceffc5e31..cfcbd55388efa0 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 @@ sys__is_gil_enabled(PyObject *module, PyObject *Py_UNUSED(ignored)) #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/sysmodule.c b/Python/sysmodule.c index aaef5aa532412b..b55bcec6a65d10 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2344,6 +2344,27 @@ 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; + } + _PyDumpExecutors(out); + fclose(out); + Py_RETURN_NONE; +} + /*[clinic input] sys._getframemodulename @@ -2603,6 +2624,7 @@ static PyMethodDef sys_methods[] = { #endif SYS__GET_CPU_COUNT_CONFIG_METHODDEF SYS__IS_GIL_ENABLED_METHODDEF + SYS__DUMP_TRACELETS_METHODDEF {NULL, NULL} // sentinel }; From 80744068087a347509e4f4436f5766b5dc0e3b33 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 5 Dec 2024 11:38:49 +0000 Subject: [PATCH 07/12] Make it work with no JIT. --- Include/internal/pycore_optimizer.h | 2 +- Python/optimizer.c | 15 +++++++++++++-- Python/sysmodule.c | 5 ++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 516a86d0db7a03..bc7cfcde613d65 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -288,7 +288,7 @@ static inline int is_terminator(const _PyUOpInstruction *uop) ); } -PyAPI_FUNC(void) _PyDumpExecutors(FILE *out); +PyAPI_FUNC(int) _PyDumpExecutors(FILE *out); #ifdef __cplusplus } diff --git a/Python/optimizer.c b/Python/optimizer.c index c9a2471bf0623b..56a6d058b416d3 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" @@ -1799,7 +1800,7 @@ dump_executor(_PyExecutorObject *executor, FILE *out) } } -void +int _PyDumpExecutors(FILE *out) { fprintf(out, "digraph ideal {\n\n"); @@ -1810,6 +1811,16 @@ _PyDumpExecutors(FILE *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 1d9410358ecfba..d6719f9bb0af91 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2360,8 +2360,11 @@ sys__dump_tracelets_impl(PyObject *module, PyObject *outpath) if (out == NULL) { return NULL; } - _PyDumpExecutors(out); + int err = _PyDumpExecutors(out); fclose(out); + if (err) { + return NULL; + } Py_RETURN_NONE; } From 0f449ed389ad794d9b6329c4ca35807fc000759d Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 5 Dec 2024 17:33:35 +0000 Subject: [PATCH 08/12] Apply suggestions from code review Co-authored-by: Ken Jin --- Python/optimizer.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index 56a6d058b416d3..4ee9d404b2edd1 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1716,7 +1716,8 @@ _Py_Executors_InvalidateCold(PyInterpreterState *interp) _Py_Executors_InvalidateAll(interp, 0); } -static void write_str(PyObject *str, FILE *out) +static void +write_str(PyObject *str, FILE *out) { // Encode the Unicode object to the specified encoding PyObject *encoded_obj = PyUnicode_AsEncodedString(str, "utf8", "strict"); @@ -1730,7 +1731,8 @@ static void write_str(PyObject *str, FILE *out) Py_DECREF(encoded_obj); } -static int find_line_number(PyCodeObject *code, _PyExecutorObject *executor) +static int +find_line_number(PyCodeObject *code, _PyExecutorObject *executor) { int code_len = (int)Py_SIZE(code); for (int i = 0; i < code_len; i++) { From c03557aaddcfc6d7ef92881afb06080e0d85364f Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 11 Dec 2024 10:37:17 +0000 Subject: [PATCH 09/12] Add explanatory comments for _PyDumpExecutors --- Python/optimizer.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index 4ee9d404b2edd1..9c215a60841cb0 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1749,12 +1749,21 @@ find_line_number(PyCodeObject *code, _PyExecutorObject *executor) return -1; } +/* Writes the node and ougoing 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 -dump_executor(_PyExecutorObject *executor, FILE *out) +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 = <\n"); fprintf(out, " \n"); if (code == NULL) { @@ -1767,6 +1776,7 @@ dump_executor(_PyExecutorObject *executor, FILE *out) fprintf(out, ": %d\n", line); } for (uint32_t i = 0; i < executor->code_size; i++) { + /* Write row for uop. Each row has a port, for incoming edges */ _PyUOpInstruction const *inst = &executor->trace[i]; const char *opname = _PyOpcode_uop_name[inst->opcode]; #ifdef Py_STATS @@ -1778,8 +1788,10 @@ dump_executor(_PyExecutorObject *executor, FILE *out) break; } } - fprintf(out, " label =
Executor
>\n"); + fprintf(out, " >\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]; @@ -1802,6 +1814,7 @@ dump_executor(_PyExecutorObject *executor, FILE *out) } } +/* Write the graph of all the live tracelets in graphviz format. */ int _PyDumpExecutors(FILE *out) { @@ -1809,7 +1822,7 @@ _PyDumpExecutors(FILE *out) fprintf(out, " rankdir = \"LR\"\n\n"); PyInterpreterState *interp = PyInterpreterState_Get(); for (_PyExecutorObject *exec = interp->executor_list_head; exec != NULL;) { - dump_executor(exec, out); + executor_to_gv(exec, out); exec = exec->vm_data.links.next; } fprintf(out, "}\n\n"); From bdc27c623c8c3a201ebee9f8e417b9e631c8cf1c Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 11 Dec 2024 10:38:21 +0000 Subject: [PATCH 10/12] Fix typos --- Python/optimizer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index 9c215a60841cb0..b433718ad6ab3d 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1749,7 +1749,7 @@ find_line_number(PyCodeObject *code, _PyExecutorObject *executor) return -1; } -/* Writes the node and ougoing edges for a single tracelet in graphviz format. +/* 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. * @@ -1776,7 +1776,7 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out) fprintf(out, ": %d\n", line); } for (uint32_t i = 0; i < executor->code_size; i++) { - /* Write row for uop. Each row has a port, for incoming edges */ + /* Write row for uop. Each row has a port, for outgoing edges */ _PyUOpInstruction const *inst = &executor->trace[i]; const char *opname = _PyOpcode_uop_name[inst->opcode]; #ifdef Py_STATS From eec294e558624ebeda232a44e32ccfbc014d6e98 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 11 Dec 2024 10:45:23 +0000 Subject: [PATCH 11/12] Revert whitespace change --- Modules/_testinternalcapi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 3b6296d4a995bb..1bb71a3e80b39d 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2081,6 +2081,7 @@ get_tracked_heap_size(PyObject *self, PyObject *Py_UNUSED(ignored)) { return PyLong_FromInt64(PyInterpreterState_Get()->gc.heap_size); } + static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, From 84fa042d84b2c58decf0afd0b96b9cb13e1bf64f Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 12 Dec 2024 11:00:01 +0000 Subject: [PATCH 12/12] Add more explanation for ports --- Python/optimizer.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index b433718ad6ab3d..6a4d20fad76c15 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1776,7 +1776,12 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out) fprintf(out, ": %d\n", line); } for (uint32_t i = 0; i < executor->code_size; i++) { - /* Write row for uop. Each row has a port, for outgoing edges */ + /* 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