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