Esempio n. 1
0
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)
Esempio n. 2
0
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)
Esempio n. 3
0
 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
Esempio n. 4
0
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))
Esempio n. 5
0
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)
Esempio n. 6
0
    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