@@ -1888,6 +1888,24 @@ _Py_Executors_InvalidateCold(PyInterpreterState *interp)
18881888 _Py_Executors_InvalidateAll (interp , 0 );
18891889}
18901890
1891+ static int
1892+ escape_angles (const char * input , Py_ssize_t size , char * buffer ) {
1893+ int written = 0 ;
1894+ for (Py_ssize_t i = 0 ; i < size ; i ++ ) {
1895+ char c = input [i ];
1896+ if (c == '<' || c == '>' ) {
1897+ buffer [written ++ ] = '&' ;
1898+ buffer [written ++ ] = c == '>' ? 'g' : 'l' ;
1899+ buffer [written ++ ] = 't' ;
1900+ buffer [written ++ ] = ';' ;
1901+ }
1902+ else {
1903+ buffer [written ++ ] = c ;
1904+ }
1905+ }
1906+ return written ;
1907+ }
1908+
18911909static void
18921910write_str (PyObject * str , FILE * out )
18931911{
@@ -1899,7 +1917,17 @@ write_str(PyObject *str, FILE *out)
18991917 }
19001918 const char * encoded_str = PyBytes_AsString (encoded_obj );
19011919 Py_ssize_t encoded_size = PyBytes_Size (encoded_obj );
1902- fwrite (encoded_str , 1 , encoded_size , out );
1920+ char buffer [120 ];
1921+ bool truncated = false;
1922+ if (encoded_size > 24 ) {
1923+ encoded_size = 24 ;
1924+ truncated = true;
1925+ }
1926+ int size = escape_angles (encoded_str , encoded_size , buffer );
1927+ fwrite (buffer , 1 , size , out );
1928+ if (truncated ) {
1929+ fwrite ("..." , 1 , 3 , out );
1930+ }
19031931 Py_DECREF (encoded_obj );
19041932}
19051933
@@ -1921,6 +1949,85 @@ find_line_number(PyCodeObject *code, _PyExecutorObject *executor)
19211949 return -1 ;
19221950}
19231951
1952+ #define RED "#ff0000"
1953+ #define WHITE "#ffffff"
1954+ #define BLUE "#0000ff"
1955+ #define BLACK "#000000"
1956+ #define LOOP "#00c000"
1957+
1958+ const char * COLORS [10 ] = {
1959+ "9" ,
1960+ "8" ,
1961+ "7" ,
1962+ "6" ,
1963+ "5" ,
1964+ "4" ,
1965+ "3" ,
1966+ "2" ,
1967+ "1" ,
1968+ WHITE ,
1969+ };
1970+
1971+ #ifdef Py_STATS
1972+ const char *
1973+ get_background_color (_PyUOpInstruction const * inst , uint64_t max_hotness )
1974+ {
1975+ uint64_t hotness = inst -> execution_count ;
1976+ int index = (hotness * 10 )/max_hotness ;
1977+ if (index > 9 ) {
1978+ index = 9 ;
1979+ }
1980+ if (index < 0 ) {
1981+ index = 0 ;
1982+ }
1983+ return COLORS [index ];
1984+ }
1985+
1986+ const char *
1987+ get_foreground_color (_PyUOpInstruction const * inst , uint64_t max_hotness )
1988+ {
1989+ if (_PyUop_Uncached [inst -> opcode ] == _DEOPT ) {
1990+ return RED ;
1991+ }
1992+ uint64_t hotness = inst -> execution_count ;
1993+ int index = (hotness * 10 )/max_hotness ;
1994+ if (index > 3 ) {
1995+ return BLACK ;
1996+ }
1997+ return WHITE ;
1998+ }
1999+ #endif
2000+
2001+ static void
2002+ write_row_for_uop (_PyExecutorObject * executor , uint32_t i , FILE * out )
2003+ {
2004+ /* Write row for uop.
2005+ * The `port` is a marker so that outgoing edges can
2006+ * be placed correctly. If a row is marked `port=17`,
2007+ * then the outgoing edge is `{EXEC_NAME}:17 -> {TARGET}`
2008+ * https://graphviz.readthedocs.io/en/stable/manual.html#node-ports-compass
2009+ */
2010+ _PyUOpInstruction const * inst = & executor -> trace [i ];
2011+ const char * opname = _PyOpcode_uop_name [inst -> opcode ];
2012+ #ifdef Py_STATS
2013+ const char * bg_color = get_background_color (inst , executor -> trace [0 ].execution_count );
2014+ const char * color = get_foreground_color (inst , executor -> trace [0 ].execution_count );
2015+ fprintf (out , " <tr><td port=\"i%d\" border=\"1\" color=\"%s\" bgcolor=\"%s\" ><font color=\"%s\"> %s -- %" PRIu64 "</font></td></tr>\n" ,
2016+ i , color , bg_color , color , opname , inst -> execution_count );
2017+ #else
2018+ const char * color = (_PyUop_Uncached [inst -> opcode ] == _DEOPT ) ? RED : BLACK ;
2019+ fprintf (out , " <tr><td port=\"i%d\" border=\"1\" color=\"%s\" >%s op0=%" PRIu64 "</td></tr>\n" , i , color , opname , inst -> operand0 );
2020+ #endif
2021+ }
2022+
2023+ static bool
2024+ is_stop (_PyUOpInstruction const * inst )
2025+ {
2026+ uint16_t base_opcode = _PyUop_Uncached [inst -> opcode ];
2027+ return (base_opcode == _EXIT_TRACE || base_opcode == _DEOPT || base_opcode == _JUMP_TO_TOP );
2028+ }
2029+
2030+
19242031/* Writes the node and outgoing edges for a single tracelet in graphviz format.
19252032 * Each tracelet is presented as a table of the uops it contains.
19262033 * If Py_STATS is enabled, execution counts are included.
@@ -1948,21 +2055,8 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out)
19482055 fprintf (out , ": %d</td></tr>\n" , line );
19492056 }
19502057 for (uint32_t i = 0 ; i < executor -> code_size ; i ++ ) {
1951- /* Write row for uop.
1952- * The `port` is a marker so that outgoing edges can
1953- * be placed correctly. If a row is marked `port=17`,
1954- * then the outgoing edge is `{EXEC_NAME}:17 -> {TARGET}`
1955- * https://graphviz.readthedocs.io/en/stable/manual.html#node-ports-compass
1956- */
1957- _PyUOpInstruction const * inst = & executor -> trace [i ];
1958- uint16_t base_opcode = _PyUop_Uncached [inst -> opcode ];
1959- const char * opname = _PyOpcode_uop_name [base_opcode ];
1960- #ifdef Py_STATS
1961- fprintf (out , " <tr><td port=\"i%d\" border=\"1\" >%s -- %" PRIu64 "</td></tr>\n" , i , opname , inst -> execution_count );
1962- #else
1963- fprintf (out , " <tr><td port=\"i%d\" border=\"1\" >%s op0=%" PRIu64 "</td></tr>\n" , i , opname , inst -> operand0 );
1964- #endif
1965- if (base_opcode == _EXIT_TRACE || base_opcode == _JUMP_TO_TOP ) {
2058+ write_row_for_uop (executor , i , out );
2059+ if (is_stop (& executor -> trace [i ])) {
19662060 break ;
19672061 }
19682062 }
@@ -1977,6 +2071,10 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out)
19772071 uint16_t base_opcode = _PyUop_Uncached [inst -> opcode ];
19782072 uint16_t flags = _PyUop_Flags [base_opcode ];
19792073 _PyExitData * exit = NULL ;
2074+ if (base_opcode == _JUMP_TO_TOP ) {
2075+ fprintf (out , "executor_%p:i%d -> executor_%p:i%d [color = \"" LOOP "\"]\n" , executor , i , executor , inst -> jump_target );
2076+ break ;
2077+ }
19802078 if (base_opcode == _EXIT_TRACE ) {
19812079 exit = (_PyExitData * )inst -> operand0 ;
19822080 }
@@ -1987,10 +2085,22 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out)
19872085 assert (base_exit_opcode == _EXIT_TRACE || base_exit_opcode == _DYNAMIC_EXIT );
19882086 exit = (_PyExitData * )exit_inst -> operand0 ;
19892087 }
1990- if (exit != NULL && exit -> executor != cold && exit -> executor != cold_dynamic ) {
1991- fprintf (out , "executor_%p:i%d -> executor_%p:start\n" , executor , i , exit -> executor );
2088+ if (exit != NULL ) {
2089+ if (exit -> executor == cold || exit -> executor == cold_dynamic ) {
2090+ #ifdef Py_STATS
2091+ /* Only mark as have cold exit if it has actually exited */
2092+ uint64_t diff = inst -> execution_count - executor -> trace [i + 1 ].execution_count ;
2093+ if (diff ) {
2094+ fprintf (out , "cold_%p%d [ label = \"%" PRIu64 "\" shape = ellipse color=\"" BLUE "\" ]\n" , executor , i , diff );
2095+ fprintf (out , "executor_%p:i%d -> cold_%p%d\n" , executor , i , executor , i );
2096+ }
2097+ #endif
2098+ }
2099+ else {
2100+ fprintf (out , "executor_%p:i%d -> executor_%p:start\n" , executor , i , exit -> executor );
2101+ }
19922102 }
1993- if (base_opcode == _EXIT_TRACE || base_opcode == _JUMP_TO_TOP ) {
2103+ if (is_stop ( inst ) ) {
19942104 break ;
19952105 }
19962106 }
@@ -2002,6 +2112,7 @@ _PyDumpExecutors(FILE *out)
20022112{
20032113 fprintf (out , "digraph ideal {\n\n" );
20042114 fprintf (out , " rankdir = \"LR\"\n\n" );
2115+ fprintf (out , " node [colorscheme=greys9]\n" );
20052116 PyInterpreterState * interp = PyInterpreterState_Get ();
20062117 for (_PyExecutorObject * exec = interp -> executor_list_head ; exec != NULL ;) {
20072118 executor_to_gv (exec , out );
0 commit comments