@@ -558,6 +558,15 @@ static PyObject* stack_pop(stack* s){
558558 return object ;
559559}
560560
561+ // Returns a pointer to the top object without poping it.
562+ static PyObject * stack_peek (stack * s ){
563+ if (s -> head == NULL ){
564+ return NULL ;
565+ }
566+
567+ return s -> head -> object ;
568+ }
569+
561570static void stack_free (stack * s ){
562571 while (s -> head != NULL ){
563572 PyObject * op = stack_pop (s );
@@ -571,12 +580,16 @@ static bool stack_empty(stack* s){
571580 return s -> head == NULL ;
572581}
573582
574- __attribute__((unused ))
575- static void stack_print (stack * s ){
583+ static bool stack_contains (stack * s , PyObject * object ){
576584 node * n = s -> head ;
577585 while (n != NULL ){
586+ if (n -> object == object ) {
587+ return true;
588+ }
578589 n = n -> next ;
579590 }
591+
592+ return false;
580593}
581594
582595static bool is_c_wrapper (PyObject * obj ){
@@ -746,7 +759,7 @@ static void invariant_reset_captured_list(void) {
746759int _Py_CheckRegionInvariant (PyThreadState * tstate )
747760{
748761 // Check if we should perform the region invariant check
749- if (!invariant_do_region_check ){
762+ if (!invariant_do_region_check || true ){
750763 return 0 ;
751764 }
752765
@@ -1640,6 +1653,253 @@ static int try_close(PyRegionObject *root_bridge) {
16401653 return -1 ;
16411654}
16421655
1656+
1657+ typedef struct drawmermaidvisitinfo {
1658+ // Nodes that have been seen before
1659+ stack * seen ;
1660+
1661+ // The number max depth of subregions that should be drawn from
1662+ // this point on.
1663+ int reg_budget ;
1664+ // The number max depth of immutable objects that should be drawn from
1665+ // this point on.
1666+ int imm_budget ;
1667+ int obj_budget ;
1668+ // The source object of the reference.
1669+ PyObject * src ;
1670+
1671+ FILE * out ;
1672+ } drawmermaidvisitinfo ;
1673+
1674+ static int _draw_mermaid_visit (PyObject * target , void * info_void ) {
1675+ drawmermaidvisitinfo * info = _Py_CAST (drawmermaidvisitinfo * , info_void );
1676+
1677+ // Self references don't look good in mermaid
1678+ if (target == info -> src ) {
1679+ return 0 ;
1680+ }
1681+
1682+ fprintf (info -> out , "%p --> %p\n" , info -> src , target );
1683+
1684+ // Check if the target should be traversed
1685+ if (stack_contains (info -> seen , target )) {
1686+ return 0 ;
1687+ }
1688+
1689+ // Mark the object as seen
1690+ if (stack_push (info -> seen , target )) {
1691+ PyErr_NoMemory ();
1692+ return -1 ;
1693+ }
1694+
1695+ info -> obj_budget -= 1 ;
1696+ if (info -> obj_budget <= 0 ) {
1697+ fprintf (info -> out , "%p:::maxdepth\n" , target );
1698+ return 0 ;
1699+ }
1700+
1701+ // c functions can't be traversed
1702+ if (is_c_wrapper (target ) || PyFunction_Check (target )) {
1703+ fprintf (info -> out , "%p:::maxdepth\n" , target );
1704+ return 0 ;
1705+ }
1706+
1707+ // Handle immutable objects
1708+ if (Py_IsImmutable (target )) {
1709+ if (info -> imm_budget == 0 ) {
1710+ fprintf (info -> out , "%p:::maxdepth\n" , target );
1711+ return 0 ;
1712+ }
1713+
1714+ info -> imm_budget -= 1 ;
1715+ PyObject * old_src = info -> src ;
1716+ info -> src = target ;
1717+ int result = !visit_object (target , (visitproc )_draw_mermaid_visit , info );
1718+ info -> src = old_src ;
1719+ info -> imm_budget += 1 ;
1720+ return result ;
1721+ }
1722+
1723+ // Don't traverse cowns
1724+ if (Py_IsCown (target )) {
1725+ return 0 ;
1726+ }
1727+
1728+ int same_region = Py_REGION_DATA (target ) == Py_REGION_DATA (info -> src );
1729+ if (!same_region ) {
1730+ if (info -> reg_budget == 0 ) {
1731+ fprintf (info -> out , "%p:::maxdepth\n" , target );
1732+ return 0 ;
1733+ }
1734+ info -> reg_budget -= 1 ;
1735+ }
1736+
1737+ PyObject * old_src = info -> src ;
1738+ info -> src = target ;
1739+ int result = !visit_object (target , (visitproc )_draw_mermaid_visit , info );
1740+ info -> src = old_src ;
1741+
1742+ if (!same_region ) {
1743+ info -> reg_budget += 1 ;
1744+ }
1745+
1746+ return result ;
1747+ }
1748+
1749+ static PyObject * draw_mermaid (PyObject * obj , int reg_depth , int imm_depth , int draw_limit ) {
1750+ // This is definitly not optimized for speed
1751+ PyObject * result = NULL ;
1752+ stack * pending = NULL ;
1753+ FILE * out = fopen ("mermaid.md" , "w" );
1754+ fprintf (out ,"Note that only reachable objects are draw!\n" );
1755+ fprintf (out ,"<div style='background: #fff'>\n\n" );
1756+ fprintf (out ,"```mermaid\n" );
1757+ fprintf (
1758+ out ,
1759+ "%%%%{init: {'theme': 'neutral', 'themeVariables': { 'fontSize': '16px' }}}%%%%\n"
1760+ );
1761+ fprintf (out ,"graph TD\n" );
1762+
1763+ drawmermaidvisitinfo info = {
1764+ .seen = stack_new (),
1765+ .reg_budget = reg_depth ,
1766+ .imm_budget = imm_depth - 1 ,
1767+ .obj_budget = draw_limit ,
1768+ .src = obj ,
1769+ .out = out ,
1770+ };
1771+ if (info .seen == NULL ) {
1772+ PyErr_NoMemory ();
1773+ goto cleanup ;
1774+ }
1775+
1776+ // Mark the object as seen
1777+ if (stack_push (info .seen , obj )) {
1778+ PyErr_NoMemory ();
1779+ goto cleanup ;
1780+ }
1781+
1782+ // Traverse objects
1783+ if (!visit_object (obj , (visitproc )_draw_mermaid_visit , & info )) {
1784+ goto cleanup ;
1785+ }
1786+
1787+ // Draw regions and add text to objects
1788+ pending = stack_new ();
1789+ if (pending == NULL ) {
1790+ PyErr_NoMemory ();
1791+ goto cleanup ;
1792+ }
1793+ while (!stack_empty (info .seen )) {
1794+ Py_region_ptr_t current_region = Py_REGION (stack_peek (info .seen ));
1795+
1796+ if (HAS_METADATA (current_region )) {
1797+ const regionmetadata * md = REGION_DATA_CAST (current_region );
1798+ fprintf (out , "style %p fill:#fcfbdd\n" , md );
1799+ if (md -> name ) {
1800+ fprintf (
1801+ out ,
1802+ "subgraph %p['%s']\n" ,
1803+ md ,
1804+ get_region_name (stack_peek (info .seen ))
1805+ );
1806+ } else {
1807+ fprintf (out , "subgraph %p['%p']\n" , md , md -> bridge );
1808+ }
1809+ }
1810+
1811+ while (!stack_empty (info .seen )) {
1812+ PyObject * item = stack_pop (info .seen );
1813+
1814+ // Skip objects from other regions
1815+ if (Py_REGION (item ) != current_region ) {
1816+ if (stack_push (pending , item )) {
1817+ PyErr_NoMemory ();
1818+ goto cleanup ;
1819+ }
1820+ continue ;
1821+ }
1822+
1823+ const char * info_text = "" ;
1824+
1825+ if (is_c_wrapper (item )) {
1826+ info_text = "<br>#91;C-Wrapper#93;" ;
1827+ } else if (PyFunction_Check (item )) {
1828+ info_text = "<br>#91;PyFunction#93;" ;
1829+ } else if (Py_IsCown (item )) {
1830+ info_text = "<br>#91;Cown#93;" ;
1831+ } else if (PyType_Check (item )) {
1832+ info_text = "<br>#91;Type#93;" ;
1833+ } else if (PyTuple_Check (item )) {
1834+ info_text = "<br>#91;Tuple#93;" ;
1835+ } else if (PyDict_Check (item )) {
1836+ info_text = "<br>#91;Dictionary#93;" ;
1837+ }
1838+
1839+ // Set text
1840+ if (Py_IsNone (item )) {
1841+ fprintf (out , " %p((None<br>%p<br>#40;immortal#41;))" , item , item );
1842+ } else if (_Py_IsImmortal (item )) {
1843+ fprintf (out , " %p[%p<br>#40;immortal#41;%s]" , item , item , info_text );
1844+ } else if (Py_is_bridge_object (item )) {
1845+ fprintf (out , " %p[\\%p<br>rc=%ld%s/]" , item , item , Py_REFCNT (item ), info_text );
1846+ } else {
1847+ fprintf (out , " %p[%p<br>rc=%ld%s]" , item , item , Py_REFCNT (item ), info_text );
1848+ }
1849+
1850+ // Add color
1851+ if (IS_LOCAL_REGION (current_region )) {
1852+ fprintf (out , ":::local" );
1853+ } else if (IS_IMMUTABLE_REGION (current_region )) {
1854+ fprintf (out , ":::immutable" );
1855+ }
1856+ fprintf (out , "\n" );
1857+ }
1858+
1859+ if (HAS_METADATA (current_region )) {
1860+ fprintf (out , "end\n" );
1861+ }
1862+
1863+ stack * tmp = info .seen ;
1864+ info .seen = pending ;
1865+ pending = tmp ;
1866+ }
1867+
1868+ result = Py_None ;
1869+ cleanup :
1870+ fprintf (out , "classDef local fill:#eefcdd\n" );
1871+ fprintf (out , "classDef immutable fill:#94f7ff\n" );
1872+ fprintf (out , "classDef maxdepth stroke-width:4px,stroke:#c00,stroke-dasharray: 10 5\n" );
1873+ fprintf (out , "```\n" );
1874+ fprintf (out , "</div>\n" );
1875+
1876+ if (info .obj_budget <= 0 ) {
1877+ fprintf (out , "Stopped drawing after traversing %d objects\n" , draw_limit );
1878+ }
1879+ fclose (out );
1880+ if (pending ) {
1881+ stack_free (pending );
1882+ }
1883+ if (info .seen ) {
1884+ stack_free (info .seen );
1885+ }
1886+ return result ;
1887+ }
1888+
1889+ PyObject * _Py_Mermaid (PyObject * args , PyObject * kwargs ) {
1890+ PyObject * obj = Py_None ;
1891+ int reg_depth = 3 ; // The maximum region depth to draw
1892+ int imm_depth = 1 ; // The maximum depth of immutable objects to draw
1893+ int draw_limit = 50 ; // The maximum number of objects to traverse
1894+ static char * kwlist [] = {"object" , "reg_depth" , "imm_depth" , "draw_limit" , NULL };
1895+
1896+ if (!PyArg_ParseTupleAndKeywords (args , kwargs , "O|iii" , kwlist , & obj , & reg_depth , & imm_depth , & draw_limit )) {
1897+ return NULL ;
1898+ }
1899+
1900+ return draw_mermaid (obj , reg_depth , imm_depth , draw_limit );
1901+ }
1902+
16431903static void PyRegion_dealloc (PyRegionObject * self ) {
16441904 // Name is immutable and not in our region.
16451905
0 commit comments