|
| 1 | +#include "Python.h" |
| 2 | + |
1 | 3 | #ifdef _Py_TIER2 |
2 | 4 |
|
3 | | -#include "Python.h" |
4 | 5 | #include "opcode.h" |
5 | 6 | #include "pycore_interp.h" |
6 | 7 | #include "pycore_backoff.h" |
@@ -474,6 +475,9 @@ add_to_trace( |
474 | 475 | trace[trace_length].target = target; |
475 | 476 | trace[trace_length].oparg = oparg; |
476 | 477 | trace[trace_length].operand0 = operand; |
| 478 | +#ifdef Py_STATS |
| 479 | + trace[trace_length].execution_count = 0; |
| 480 | +#endif |
477 | 481 | return trace_length + 1; |
478 | 482 | } |
479 | 483 |
|
@@ -983,6 +987,9 @@ static void make_exit(_PyUOpInstruction *inst, int opcode, int target) |
983 | 987 | inst->operand0 = 0; |
984 | 988 | inst->format = UOP_FORMAT_TARGET; |
985 | 989 | inst->target = target; |
| 990 | +#ifdef Py_STATS |
| 991 | + inst->execution_count = 0; |
| 992 | +#endif |
986 | 993 | } |
987 | 994 |
|
988 | 995 | /* Convert implicit exits, errors and deopts |
@@ -1709,4 +1716,131 @@ _Py_Executors_InvalidateCold(PyInterpreterState *interp) |
1709 | 1716 | _Py_Executors_InvalidateAll(interp, 0); |
1710 | 1717 | } |
1711 | 1718 |
|
| 1719 | +static void |
| 1720 | +write_str(PyObject *str, FILE *out) |
| 1721 | +{ |
| 1722 | + // Encode the Unicode object to the specified encoding |
| 1723 | + PyObject *encoded_obj = PyUnicode_AsEncodedString(str, "utf8", "strict"); |
| 1724 | + if (encoded_obj == NULL) { |
| 1725 | + PyErr_Clear(); |
| 1726 | + return; |
| 1727 | + } |
| 1728 | + const char *encoded_str = PyBytes_AsString(encoded_obj); |
| 1729 | + Py_ssize_t encoded_size = PyBytes_Size(encoded_obj); |
| 1730 | + fwrite(encoded_str, 1, encoded_size, out); |
| 1731 | + Py_DECREF(encoded_obj); |
| 1732 | +} |
| 1733 | + |
| 1734 | +static int |
| 1735 | +find_line_number(PyCodeObject *code, _PyExecutorObject *executor) |
| 1736 | +{ |
| 1737 | + int code_len = (int)Py_SIZE(code); |
| 1738 | + for (int i = 0; i < code_len; i++) { |
| 1739 | + _Py_CODEUNIT *instr = &_PyCode_CODE(code)[i]; |
| 1740 | + int opcode = instr->op.code; |
| 1741 | + if (opcode == ENTER_EXECUTOR) { |
| 1742 | + _PyExecutorObject *exec = code->co_executors->executors[instr->op.arg]; |
| 1743 | + if (exec == executor) { |
| 1744 | + return PyCode_Addr2Line(code, i*2); |
| 1745 | + } |
| 1746 | + } |
| 1747 | + i += _PyOpcode_Caches[_Py_GetBaseCodeUnit(code, i).op.code]; |
| 1748 | + } |
| 1749 | + return -1; |
| 1750 | +} |
| 1751 | + |
| 1752 | +/* Writes the node and outgoing edges for a single tracelet in graphviz format. |
| 1753 | + * Each tracelet is presented as a table of the uops it contains. |
| 1754 | + * If Py_STATS is enabled, execution counts are included. |
| 1755 | + * |
| 1756 | + * https://graphviz.readthedocs.io/en/stable/manual.html |
| 1757 | + * https://graphviz.org/gallery/ |
| 1758 | + */ |
| 1759 | +static void |
| 1760 | +executor_to_gv(_PyExecutorObject *executor, FILE *out) |
| 1761 | +{ |
| 1762 | + PyCodeObject *code = executor->vm_data.code; |
| 1763 | + fprintf(out, "executor_%p [\n", executor); |
| 1764 | + fprintf(out, " shape = none\n"); |
| 1765 | + |
| 1766 | + /* Write the HTML table for the uops */ |
| 1767 | + fprintf(out, " label = <<table border=\"0\" cellspacing=\"0\">\n"); |
| 1768 | + fprintf(out, " <tr><td port=\"start\" border=\"1\" ><b>Executor</b></td></tr>\n"); |
| 1769 | + if (code == NULL) { |
| 1770 | + fprintf(out, " <tr><td border=\"1\" >No code object</td></tr>\n"); |
| 1771 | + } |
| 1772 | + else { |
| 1773 | + fprintf(out, " <tr><td border=\"1\" >"); |
| 1774 | + write_str(code->co_qualname, out); |
| 1775 | + int line = find_line_number(code, executor); |
| 1776 | + fprintf(out, ": %d</td></tr>\n", line); |
| 1777 | + } |
| 1778 | + for (uint32_t i = 0; i < executor->code_size; i++) { |
| 1779 | + /* Write row for uop. |
| 1780 | + * The `port` is a marker so that outgoing edges can |
| 1781 | + * be placed correctly. If a row is marked `port=17`, |
| 1782 | + * then the outgoing edge is `{EXEC_NAME}:17 -> {TARGET}` |
| 1783 | + * https://graphviz.readthedocs.io/en/stable/manual.html#node-ports-compass |
| 1784 | + */ |
| 1785 | + _PyUOpInstruction const *inst = &executor->trace[i]; |
| 1786 | + const char *opname = _PyOpcode_uop_name[inst->opcode]; |
| 1787 | +#ifdef Py_STATS |
| 1788 | + fprintf(out, " <tr><td port=\"i%d\" border=\"1\" >%s -- %" PRIu64 "</td></tr>\n", i, opname, inst->execution_count); |
| 1789 | +#else |
| 1790 | + fprintf(out, " <tr><td port=\"i%d\" border=\"1\" >%s</td></tr>\n", i, opname); |
| 1791 | +#endif |
| 1792 | + if (inst->opcode == _EXIT_TRACE || inst->opcode == _JUMP_TO_TOP) { |
| 1793 | + break; |
| 1794 | + } |
| 1795 | + } |
| 1796 | + fprintf(out, " </table>>\n"); |
| 1797 | + fprintf(out, "]\n\n"); |
| 1798 | + |
| 1799 | + /* Write all the outgoing edges */ |
| 1800 | + for (uint32_t i = 0; i < executor->code_size; i++) { |
| 1801 | + _PyUOpInstruction const *inst = &executor->trace[i]; |
| 1802 | + uint16_t flags = _PyUop_Flags[inst->opcode]; |
| 1803 | + _PyExitData *exit = NULL; |
| 1804 | + if (inst->opcode == _EXIT_TRACE) { |
| 1805 | + exit = (_PyExitData *)inst->operand0; |
| 1806 | + } |
| 1807 | + else if (flags & HAS_EXIT_FLAG) { |
| 1808 | + assert(inst->format == UOP_FORMAT_JUMP); |
| 1809 | + _PyUOpInstruction const *exit_inst = &executor->trace[inst->jump_target]; |
| 1810 | + assert(exit_inst->opcode == _EXIT_TRACE); |
| 1811 | + exit = (_PyExitData *)exit_inst->operand0; |
| 1812 | + } |
| 1813 | + if (exit != NULL && exit->executor != NULL) { |
| 1814 | + fprintf(out, "executor_%p:i%d -> executor_%p:start\n", executor, i, exit->executor); |
| 1815 | + } |
| 1816 | + if (inst->opcode == _EXIT_TRACE || inst->opcode == _JUMP_TO_TOP) { |
| 1817 | + break; |
| 1818 | + } |
| 1819 | + } |
| 1820 | +} |
| 1821 | + |
| 1822 | +/* Write the graph of all the live tracelets in graphviz format. */ |
| 1823 | +int |
| 1824 | +_PyDumpExecutors(FILE *out) |
| 1825 | +{ |
| 1826 | + fprintf(out, "digraph ideal {\n\n"); |
| 1827 | + fprintf(out, " rankdir = \"LR\"\n\n"); |
| 1828 | + PyInterpreterState *interp = PyInterpreterState_Get(); |
| 1829 | + for (_PyExecutorObject *exec = interp->executor_list_head; exec != NULL;) { |
| 1830 | + executor_to_gv(exec, out); |
| 1831 | + exec = exec->vm_data.links.next; |
| 1832 | + } |
| 1833 | + fprintf(out, "}\n\n"); |
| 1834 | + return 0; |
| 1835 | +} |
| 1836 | + |
| 1837 | +#else |
| 1838 | + |
| 1839 | +int |
| 1840 | +_PyDumpExecutors(FILE *out) |
| 1841 | +{ |
| 1842 | + PyErr_SetString(PyExc_NotImplementedError, "No JIT available"); |
| 1843 | + return -1; |
| 1844 | +} |
| 1845 | + |
1712 | 1846 | #endif /* _Py_TIER2 */ |
0 commit comments