def program(f: F, *args, auto_optimize=False, device=dtypes.DeviceType.CPU, constant_functions=False, **kwargs) -> parser.DaceProgram: """ Entry point to a data-centric program. For methods and ``classmethod``s, use ``@dace.method``. :param f: The function to define as the entry point. :param auto_optimize: If True, applies automatic optimization heuristics on the generated DaCe program during compilation. :param device: Transform the function to run on the target device. :param constant_functions: If True, assumes all external functions that do not depend on internal variables are constant. This will hardcode their return values into the resulting program. :note: If arguments are defined with type hints, the program can be compiled ahead-of-time with ``.compile()``. """ # Parses a python @dace.program function and returns an object that can # be translated return parser.DaceProgram(f, args, kwargs, auto_optimize, device, constant_functions)
def program(f: F, *args, auto_optimize=False, device=dtypes.DeviceType.CPU, **kwargs) -> parser.DaceProgram: """ DaCe program, entry point to a data-centric program. """ # Parses a python @dace.program function and returns an object that can # be translated return parser.DaceProgram(f, args, kwargs, auto_optimize, device)
def __get__(self, obj, objtype=None) -> parser.DaceProgram: # Modify wrapped instance as necessary, only clearing # compiled program cache if needed. objid = id(obj) if objid in self.wrapped: return self.wrapped[objid] prog = parser.DaceProgram(f, args, kwargs, auto_optimize, device, constant_functions, method=True) prog.methodobj = obj self.wrapped[objid] = prog return prog
def method(f: F, *args, auto_optimize=False, device=dtypes.DeviceType.CPU, **kwargs) -> parser.DaceProgram: """ Entry point to a data-centric program that is a method or a ``classmethod``. """ # Create a wrapper class that can bind to the object instance class MethodWrapper: def __init__(self, prog): self.prog = prog def __get__(self, obj, objtype=None): # Modify wrapped instance as necessary, only clearing # compiled program cache if needed. if self.prog.methodobj is not obj: self.prog.methodobj = obj return self.prog return MethodWrapper( parser.DaceProgram(f, args, kwargs, auto_optimize, device, method=True))
def program(f, *args, **kwargs) -> parser.DaceProgram: """ DaCe program, entry point to a data-centric program. """ # Parses a python @dace.program function and returns an object that can # be translated return parser.DaceProgram(f, args, kwargs)
def global_value_to_node(self, value, parent_node, qualname, recurse=False, detect_callables=False): # if recurse is false, we don't allow recursion into lists # this should not happen anyway; the globals dict should only contain # single "level" lists if not recurse and isinstance(value, (list, tuple)): # bail after more than one level of lists return None if isinstance(value, list): elts = [ self.global_value_to_node(v, parent_node, qualname + f'[{i}]', detect_callables=detect_callables) for i, v in enumerate(value) ] if any(e is None for e in elts): return None newnode = ast.List(elts=elts, ctx=parent_node.ctx) elif isinstance(value, tuple): elts = [ self.global_value_to_node(v, parent_node, qualname + f'[{i}]', detect_callables=detect_callables) for i, v in enumerate(value) ] if any(e is None for e in elts): return None newnode = ast.Tuple(elts=elts, ctx=parent_node.ctx) elif isinstance(value, symbolic.symbol): # Symbols resolve to the symbol name newnode = ast.Name(id=value.name, ctx=ast.Load()) elif (dtypes.isconstant(value) or isinstance(value, SDFG) or hasattr(value, '__sdfg__')): # Could be a constant, an SDFG, or SDFG-convertible object if isinstance(value, SDFG) or hasattr(value, '__sdfg__'): self.closure.closure_sdfgs[qualname] = value else: self.closure.closure_constants[qualname] = value # Compatibility check since Python changed their AST nodes if sys.version_info >= (3, 8): newnode = ast.Constant(value=value, kind='') else: if value is None: newnode = ast.NameConstant(value=None) elif isinstance(value, str): newnode = ast.Str(s=value) else: newnode = ast.Num(n=value) newnode.oldnode = copy.deepcopy(parent_node) elif detect_callables and hasattr(value, '__call__') and hasattr( value.__call__, '__sdfg__'): return self.global_value_to_node(value.__call__, parent_node, qualname, recurse, detect_callables) elif isinstance(value, numpy.ndarray): # Arrays need to be stored as a new name and fed as an argument if id(value) in self.closure.array_mapping: arrname = self.closure.array_mapping[id(value)] else: arrname = self._qualname_to_array_name(qualname) desc = data.create_datadescriptor(value) self.closure.closure_arrays[arrname] = ( qualname, desc, lambda: eval(qualname, self.globals), False) self.closure.array_mapping[id(value)] = arrname newnode = ast.Name(id=arrname, ctx=ast.Load()) elif detect_callables and callable(value): # Try parsing the function as a dace function/method newnode = None try: from dace.frontend.python import parser # Avoid import loops parent_object = None if hasattr(value, '__self__'): parent_object = value.__self__ # If it is a callable object if (not inspect.isfunction(value) and not inspect.ismethod(value) and not inspect.isbuiltin(value) and hasattr(value, '__call__')): parent_object = value value = value.__call__ # Replacements take precedence over auto-parsing try: if has_replacement(value, parent_object, parent_node): return None except Exception: pass # Store the handle to the original callable, in case parsing fails cbqualname = astutils.rname(parent_node) cbname = self._qualname_to_array_name(cbqualname, prefix='') self.closure.callbacks[cbname] = (cbqualname, value, False) # From this point on, any failure will result in a callback newnode = ast.Name(id=cbname, ctx=ast.Load()) # Decorated or functions with missing source code sast, _, _, _ = astutils.function_to_ast(value) if len(sast.body[0].decorator_list) > 0: return newnode parsed = parser.DaceProgram(value, [], {}, False, dtypes.DeviceType.CPU) # If method, add the first argument (which disappears due to # being a bound method) and the method's object if parent_object is not None: parsed.methodobj = parent_object parsed.objname = inspect.getfullargspec(value).args[0] res = self.global_value_to_node(parsed, parent_node, qualname, recurse, detect_callables) # Keep callback in callbacks in case of parsing failure # del self.closure.callbacks[cbname] return res except Exception: # Parsing failed (almost any exception can occur) return newnode else: return None if parent_node is not None: return ast.copy_location(newnode, parent_node) else: return newnode