@@ -1917,6 +1917,24 @@ _Py_Executors_InvalidateCold(PyInterpreterState *interp)
19171917 _Py_Executors_InvalidateAll (interp , 0 );
19181918}
19191919
1920+ static int
1921+ escape_angles (const char * input , Py_ssize_t size , char * buffer ) {
1922+ int written = 0 ;
1923+ for (Py_ssize_t i = 0 ; i < size ; i ++ ) {
1924+ char c = input [i ];
1925+ if (c == '<' || c == '>' ) {
1926+ buffer [written ++ ] = '&' ;
1927+ buffer [written ++ ] = c == '>' ? 'g' : 'l' ;
1928+ buffer [written ++ ] = 't' ;
1929+ buffer [written ++ ] = ';' ;
1930+ }
1931+ else {
1932+ buffer [written ++ ] = c ;
1933+ }
1934+ }
1935+ return written ;
1936+ }
1937+
19201938static void
19211939write_str (PyObject * str , FILE * out )
19221940{
@@ -1928,7 +1946,17 @@ write_str(PyObject *str, FILE *out)
19281946 }
19291947 const char * encoded_str = PyBytes_AsString (encoded_obj );
19301948 Py_ssize_t encoded_size = PyBytes_Size (encoded_obj );
1931- fwrite (encoded_str , 1 , encoded_size , out );
1949+ char buffer [120 ];
1950+ bool truncated = false;
1951+ if (encoded_size > 24 ) {
1952+ encoded_size = 24 ;
1953+ truncated = true;
1954+ }
1955+ int size = escape_angles (encoded_str , encoded_size , buffer );
1956+ fwrite (buffer , 1 , size , out );
1957+ if (truncated ) {
1958+ fwrite ("..." , 1 , 3 , out );
1959+ }
19321960 Py_DECREF (encoded_obj );
19331961}
19341962
@@ -1950,6 +1978,85 @@ find_line_number(PyCodeObject *code, _PyExecutorObject *executor)
19501978 return -1 ;
19511979}
19521980
1981+ #define RED "#ff0000"
1982+ #define WHITE "#ffffff"
1983+ #define BLUE "#0000ff"
1984+ #define BLACK "#000000"
1985+ #define LOOP "#00c000"
1986+
1987+ const char * COLORS [10 ] = {
1988+ "9" ,
1989+ "8" ,
1990+ "7" ,
1991+ "6" ,
1992+ "5" ,
1993+ "4" ,
1994+ "3" ,
1995+ "2" ,
1996+ "1" ,
1997+ WHITE ,
1998+ };
1999+
2000+ #ifdef Py_STATS
2001+ const char *
2002+ get_background_color (_PyUOpInstruction const * inst , uint64_t max_hotness )
2003+ {
2004+ uint64_t hotness = inst -> execution_count ;
2005+ int index = (hotness * 10 )/max_hotness ;
2006+ if (index > 9 ) {
2007+ index = 9 ;
2008+ }
2009+ if (index < 0 ) {
2010+ index = 0 ;
2011+ }
2012+ return COLORS [index ];
2013+ }
2014+
2015+ const char *
2016+ get_foreground_color (_PyUOpInstruction const * inst , uint64_t max_hotness )
2017+ {
2018+ if (_PyUop_Uncached [inst -> opcode ] == _DEOPT ) {
2019+ return RED ;
2020+ }
2021+ uint64_t hotness = inst -> execution_count ;
2022+ int index = (hotness * 10 )/max_hotness ;
2023+ if (index > 3 ) {
2024+ return BLACK ;
2025+ }
2026+ return WHITE ;
2027+ }
2028+ #endif
2029+
2030+ static void
2031+ write_row_for_uop (_PyExecutorObject * executor , uint32_t i , FILE * out )
2032+ {
2033+ /* Write row for uop.
2034+ * The `port` is a marker so that outgoing edges can
2035+ * be placed correctly. If a row is marked `port=17`,
2036+ * then the outgoing edge is `{EXEC_NAME}:17 -> {TARGET}`
2037+ * https://graphviz.readthedocs.io/en/stable/manual.html#node-ports-compass
2038+ */
2039+ _PyUOpInstruction const * inst = & executor -> trace [i ];
2040+ const char * opname = _PyOpcode_uop_name [inst -> opcode ];
2041+ #ifdef Py_STATS
2042+ const char * bg_color = get_background_color (inst , executor -> trace [0 ].execution_count );
2043+ const char * color = get_foreground_color (inst , executor -> trace [0 ].execution_count );
2044+ fprintf (out , " <tr><td port=\"i%d\" border=\"1\" color=\"%s\" bgcolor=\"%s\" ><font color=\"%s\"> %s -- %" PRIu64 "</font></td></tr>\n" ,
2045+ i , color , bg_color , color , opname , inst -> execution_count );
2046+ #else
2047+ const char * color = (_PyUop_Uncached [inst -> opcode ] == _DEOPT ) ? RED : BLACK ;
2048+ fprintf (out , " <tr><td port=\"i%d\" border=\"1\" color=\"%s\" >%s op0=%" PRIu64 "</td></tr>\n" , i , color , opname , inst -> operand0 );
2049+ #endif
2050+ }
2051+
2052+ static bool
2053+ is_stop (_PyUOpInstruction const * inst )
2054+ {
2055+ uint16_t base_opcode = _PyUop_Uncached [inst -> opcode ];
2056+ return (base_opcode == _EXIT_TRACE || base_opcode == _DEOPT || base_opcode == _JUMP_TO_TOP );
2057+ }
2058+
2059+
19532060/* Writes the node and outgoing edges for a single tracelet in graphviz format.
19542061 * Each tracelet is presented as a table of the uops it contains.
19552062 * If Py_STATS is enabled, execution counts are included.
@@ -1977,21 +2084,8 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out)
19772084 fprintf (out , ": %d</td></tr>\n" , line );
19782085 }
19792086 for (uint32_t i = 0 ; i < executor -> code_size ; i ++ ) {
1980- /* Write row for uop.
1981- * The `port` is a marker so that outgoing edges can
1982- * be placed correctly. If a row is marked `port=17`,
1983- * then the outgoing edge is `{EXEC_NAME}:17 -> {TARGET}`
1984- * https://graphviz.readthedocs.io/en/stable/manual.html#node-ports-compass
1985- */
1986- _PyUOpInstruction const * inst = & executor -> trace [i ];
1987- uint16_t base_opcode = _PyUop_Uncached [inst -> opcode ];
1988- const char * opname = _PyOpcode_uop_name [base_opcode ];
1989- #ifdef Py_STATS
1990- fprintf (out , " <tr><td port=\"i%d\" border=\"1\" >%s -- %" PRIu64 "</td></tr>\n" , i , opname , inst -> execution_count );
1991- #else
1992- fprintf (out , " <tr><td port=\"i%d\" border=\"1\" >%s op0=%" PRIu64 "</td></tr>\n" , i , opname , inst -> operand0 );
1993- #endif
1994- if (base_opcode == _EXIT_TRACE || base_opcode == _JUMP_TO_TOP ) {
2087+ write_row_for_uop (executor , i , out );
2088+ if (is_stop (& executor -> trace [i ])) {
19952089 break ;
19962090 }
19972091 }
@@ -2006,6 +2100,10 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out)
20062100 uint16_t base_opcode = _PyUop_Uncached [inst -> opcode ];
20072101 uint16_t flags = _PyUop_Flags [base_opcode ];
20082102 _PyExitData * exit = NULL ;
2103+ if (base_opcode == _JUMP_TO_TOP ) {
2104+ fprintf (out , "executor_%p:i%d -> executor_%p:i%d [color = \"" LOOP "\"]\n" , executor , i , executor , inst -> jump_target );
2105+ break ;
2106+ }
20092107 if (base_opcode == _EXIT_TRACE ) {
20102108 exit = (_PyExitData * )inst -> operand0 ;
20112109 }
@@ -2016,10 +2114,22 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out)
20162114 assert (base_exit_opcode == _EXIT_TRACE || base_exit_opcode == _DYNAMIC_EXIT );
20172115 exit = (_PyExitData * )exit_inst -> operand0 ;
20182116 }
2019- if (exit != NULL && exit -> executor != cold && exit -> executor != cold_dynamic ) {
2020- fprintf (out , "executor_%p:i%d -> executor_%p:start\n" , executor , i , exit -> executor );
2117+ if (exit != NULL ) {
2118+ if (exit -> executor == cold || exit -> executor == cold_dynamic ) {
2119+ #ifdef Py_STATS
2120+ /* Only mark as have cold exit if it has actually exited */
2121+ uint64_t diff = inst -> execution_count - executor -> trace [i + 1 ].execution_count ;
2122+ if (diff ) {
2123+ fprintf (out , "cold_%p%d [ label = \"%" PRIu64 "\" shape = ellipse color=\"" BLUE "\" ]\n" , executor , i , diff );
2124+ fprintf (out , "executor_%p:i%d -> cold_%p%d\n" , executor , i , executor , i );
2125+ }
2126+ #endif
2127+ }
2128+ else {
2129+ fprintf (out , "executor_%p:i%d -> executor_%p:start\n" , executor , i , exit -> executor );
2130+ }
20212131 }
2022- if (base_opcode == _EXIT_TRACE || base_opcode == _JUMP_TO_TOP ) {
2132+ if (is_stop ( inst ) ) {
20232133 break ;
20242134 }
20252135 }
@@ -2031,6 +2141,7 @@ _PyDumpExecutors(FILE *out)
20312141{
20322142 fprintf (out , "digraph ideal {\n\n" );
20332143 fprintf (out , " rankdir = \"LR\"\n\n" );
2144+ fprintf (out , " node [colorscheme=greys9]\n" );
20342145 PyInterpreterState * interp = PyInterpreterState_Get ();
20352146 for (_PyExecutorObject * exec = interp -> executor_list_head ; exec != NULL ;) {
20362147 executor_to_gv (exec , out );
0 commit comments