44
55import ast
66import inspect
7+ import textwrap
78from collections .abc import Callable
89from types import FunctionType as PyFunctionType
910from .._mlir .ir import (
2728from .symbol_resolver import ASTResolver
2829
2930
30- def _get_global_vars (_func ):
31+ def _get_global_vars (_func , skip : set [str ] = None , stop : set [str ] = None ):
32+ """
33+ Collect global variables from the call stack of a Python function.
34+
35+ Args:
36+ skip: Set of frame names to skip over when walking the call stack.
37+ Frames whose co_name is in `skip` are ignored (no variables collected), and the walk continues to the next outer frame.
38+ This is mainly used to skip compiler internal functions when collecting global variables used in source code.
39+ stop: Set of frame names that act as boundaries for the stack walk.
40+ When a frame whose co_name is in `stop` is reached, its variables are collected and then the walk terminates.
41+ """
42+ if skip is None :
43+ skip = {"get_global_vars" , "customize" , "build" }
44+ if stop is None :
45+ stop = {"<module>" }
3146 if isinstance (_func , Callable ):
3247 # Discussions: https://github.com/taichi-dev/taichi/issues/282
3348 global_vars = _func .__globals__ .copy ()
3449 else :
3550 global_vars = {}
3651
37- # Get back to the outer-most scope (user-defined function)
52+ # Get back to outer scopes
3853 # Mainly used to get the annotation definitions (shape and type),
3954 # which are probably not defined in __globals__
40- for name , var in inspect .stack ()[3 ][0 ].f_locals .items ():
41- if isinstance (var , (int , float , AlloType )) or inspect .isfunction (var ):
42- global_vars [name ] = var
55+ frame = inspect .currentframe ().f_back
56+ while frame :
57+ if frame .f_code .co_name in skip :
58+ frame = frame .f_back
59+ continue
60+ # collect allowed types
61+ for name , var in frame .f_locals .items ():
62+ if isinstance (var , (int , float , AlloType )) or inspect .isfunction (var ):
63+ global_vars [name ] = var
64+ # boundary
65+ if frame .f_code .co_name in stop :
66+ break
67+ frame = frame .f_back
4368
4469 if isinstance (_func , Callable ):
4570 freevar_names = _func .__code__ .co_freevars
@@ -52,13 +77,25 @@ def _get_global_vars(_func):
5277
5378
5479def get_global_vars (func ):
55- global_vars = _get_global_vars (func )
56- new_global_vars = global_vars .copy ()
57- for var in global_vars .values ():
58- # import functions from other files
59- if isinstance (var , PyFunctionType ):
60- new_global_vars .update (_get_global_vars (var ))
61- return new_global_vars
80+ all_globals = {}
81+ worklist = [func ]
82+ visited_funcs = set ()
83+
84+ while worklist :
85+ f = worklist .pop ()
86+ if f in visited_funcs :
87+ continue
88+ visited_funcs .add (f )
89+
90+ gv = _get_global_vars (f )
91+ for name , val in gv .items ():
92+ if name not in all_globals :
93+ all_globals [name ] = val
94+ # import functions from other files
95+ if isinstance (val , PyFunctionType ):
96+ worklist .append (val )
97+
98+ return all_globals
6299
63100
64101def get_extra_type_hints (dtype : AlloType ):
@@ -104,7 +141,13 @@ def _adjust_line_numbers(node, offset):
104141 child .end_lineno += offset
105142
106143
107- def parse_ast (src , starting_line_no = 1 , verbose = False ):
144+ def parse_ast (src , verbose = False ):
145+ if isinstance (src , str ):
146+ starting_line_no = 1
147+ else :
148+ src , starting_line_no = inspect .getsourcelines (src )
149+ src = [textwrap .fill (line , tabsize = 4 , width = 9999 ) for line in src ]
150+ src = textwrap .dedent ("\n " .join (src ))
108151 tree = ast .parse (src )
109152 _adjust_line_numbers (tree , starting_line_no - 1 )
110153 if verbose :
0 commit comments