Пример #1
0
    def indices(self):
        '''
        Supports semantic-navigation by returning the list of nodes
        representing the index expressions for this array reference.

        :returns: the PSyIR nodes representing the array-index expressions.
        :rtype: list of :py:class:`psyclone.psyir.nodes.Node`

        :raises InternalError: if this node has no children or if they are \
                               not valid array-index expressions.

        '''
        if not self._children:
            raise InternalError(
                "{0} malformed or incomplete: must have one or more "
                "children representing array-index expressions but found "
                "none.".format(type(self).__name__))
        for idx, child in enumerate(self._children):
            if not self._validate_child(idx, child):
                raise InternalError(
                    "{0} malformed or incomplete: child {1} must by a psyir."
                    "nodes.DataNode or Range representing an array-index "
                    "expression but found '{2}'".format(
                        type(self).__name__, idx,
                        type(child).__name__))
        return self.children
Пример #2
0
    def __init__(self, nodes=None):
        # This dictionary stores the mapping of variable names to the
        # corresponding VariableAccessInfo instance.
        dict.__init__(self)

        # Stores the current location information
        self._location = 0
        if nodes:
            # Import here to avoid circular dependency
            from psyclone.psyir.nodes import Node
            if isinstance(nodes, list):
                for node in nodes:
                    if not isinstance(node, Node):
                        raise InternalError("Error in VariablesAccessInfo. "
                                            "One element in the node list is "
                                            "not a Node, but of type {0}"
                                            .format(type(node)))

                    node.reference_accesses(self)
            elif isinstance(nodes, Node):
                nodes.reference_accesses(self)
            else:
                arg_type = str(type(nodes))
                raise InternalError("Error in VariablesAccessInfo. "
                                    "Argument must be a single Node in a "
                                    "schedule or a list of Nodes in a "
                                    "schedule but have been passed an "
                                    "object of type: {0}".
                                    format(arg_type))
Пример #3
0
    def make_property(dereference_format, type_name, intrinsic_type):
        '''Creates a property (based on namedtuple) for a given Fortran
        code to access a grid property, and the type.

        :param str dereference_format: The Fortran code to access a property \
            given a field name (which will be used to replace a {0} in the \
            string. E.g. "{0}%whole%xstop").
        :param str type_name: The type of the grid property, must be \
            'scalar' or 'array'.
        :param str intrinsic_type: The intrinsic type of the grid property, \
            must be 'integer' or 'real'.

        :returns: a namedtuple for a grid property given the Fortran
            statement to access it and the type.

        :raises InternalError: if type_name is not 'scalar' or 'array'
        :raises InternalError: if intrinsic_type is not 'integer' or 'real'
        '''
        if type_name not in ['array', 'scalar']:
            raise InternalError("Type must be 'array' or 'scalar' but is "
                                "'{0}'.".format(type_name))

        if intrinsic_type not in ['integer', 'real']:
            raise InternalError("Intrinsic type must be 'integer' or 'real' "
                                "but is '{0}'.".format(intrinsic_type))

        Property = namedtuple("Property", "fortran type intrinsic_type")
        return Property(dereference_format, type_name, intrinsic_type)
Пример #4
0
    def indirection_dofmap(self,
                           function_space,
                           operator=None,
                           var_accesses=None):
        '''Add indirection dofmap required when applying a CMA operator. If
        supplied it also stores this access in var_accesses.

        :param function_space: the function space for which the indirect \
            dofmap is required.
        :type function_space: :py:class:`psyclone.domain.lfric.FunctionSpace`
        :param operator: the CMA operator.
        :type operator: :py:class:`psyclone.dynamo0p3.DynKernelArgument`
        :param var_accesses: optional VariablesAccessInfo instance to store \
            the information about variable accesses.
        :type var_accesses: \
            :py:class:`psyclone.core.access_info.VariablesAccessInfo`

        :raises InternalError: if no kernel argument is supplied.
        :raises InternalError: if the supplied kernel argument is not a \
            CMA operator.

        '''
        if not operator:
            raise InternalError("No CMA operator supplied.")
        if operator.argument_type != "gh_columnwise_operator":
            raise InternalError("A CMA operator (gh_columnwise_operator) must "
                                "be supplied but got {0}".format(
                                    operator.argument_type))
        super(KernStubArgList,
              self).indirection_dofmap(function_space, operator, var_accesses)
Пример #5
0
    def indices(self):
        '''
        Supports semantic-navigation by returning the list of nodes
        representing the index expressions for this array reference.

        :returns: the PSyIR nodes representing the array-index expressions.
        :rtype: list of :py:class:`psyclone.psyir.nodes.Node`

        :raises InternalError: if this node does not have at least two \
                               children.

        '''
        if len(self._children) < 2:
            raise InternalError(
                "{0} malformed or incomplete: must "
                "have one or more children representing array-index "
                "expressions but found none.")
        for idx, child in enumerate(self._children[1:], start=1):
            if not self._validate_child(idx, child):
                raise InternalError(
                    "{0} malformed or incomplete: child "
                    "{1} must represent an array-index expression but found "
                    "'{2}' instead of psyir.nodes.DataNode or Range".format(
                        type(self).__name__, idx,
                        type(child).__name__))
        return self._children[1:]
Пример #6
0
    def match(node):
        '''Whether or not the PSyIR sub-tree pointed to by node represents a
        kernel. A kernel is defined as a section of code that sits
        within a recognised loop structure and does not itself contain
        loops, array assignments, or 'CodeBlocks' (code not
        represented in the PSyIR such as subroutine calls or IO
        operations).

        :param node: node in the PSyIR to check.
        :type node: :py:class:`psyclone.psyir.nodes.Schedule`
        :returns: true if this node conforms to the rules for a kernel.
        :rtype: bool

        '''
        from psyclone.psyir.nodes import CodeBlock, Assignment
        # This function is called with node being a Schedule.
        if not isinstance(node, Schedule):
            raise InternalError("Expected 'Schedule' in 'match', got '{0}'.".
                                format(type(node)))

        # Check for array assignment, codeblock and loop
        nodes = [assign for assign in node.walk(Assignment)
                 if assign.is_array_range]
        nodes += node.walk((CodeBlock, NemoLoop))

        # A kernel cannot contain loops, array assignments or other
        # unrecognised code (including IO operations and routine
        # calls) or loops. So if there is any node in the result of
        # the walk, this node can not be a kernel.
        return len(nodes) == 0
Пример #7
0
    def add_access(self, signature, access_type, node, indices=None):
        '''Adds access information for the variable with the given signature.

        :param signature: the signature of the variable.
        :type signature: :py:class:`psyclone.core.Signature`
        :param access_type: the type of access (READ, WRITE, ...)
        :type access_type: :py:class:`psyclone.core.access_type.AccessType`
        :param node: Node in PSyIR in which the access happens.
        :type node: :py:class:`psyclone.psyir.nodes.Node` instance
        :param indices: indices used in the access (None if the variable \
            is not an array). Defaults to None.
        :type indices: list of :py:class:`psyclone.psyir.nodes.Node`

        '''
        if not isinstance(signature, Signature):
            raise InternalError("Got '{0}' of type '{1}' but expected it to "
                                "be of type psyclone.core.Signature.".format(
                                    signature,
                                    type(signature).__name__))

        if signature in self:
            self[signature].add_access_with_location(access_type,
                                                     self._location, node,
                                                     indices)
        else:
            var_info = SingleVariableAccessInfo(signature)
            var_info.add_access_with_location(access_type, self._location,
                                              node, indices)
            self[signature] = var_info
Пример #8
0
    def update_arg_to_module_map(self, statement):
        '''Takes a use statement and adds its contents to the internal
        arg_name_to_module_name map. This map associates names
        specified in the 'only' list with the corresponding use name.

        :param statement: A use statement
        :type statement: :py:class:`fparser.two.Fortran2003.Use_Stmt`
        :raises InternalError: if the statement being passed is not an \
        fparser use statement.

        '''
        # make sure statement is a use
        if not isinstance(statement, Use_Stmt):
            raise InternalError(
                "algorithm.py:Parser:update_arg_to_module_map: Expected "
                "a use statement but found instance of "
                "'{0}'.".format(type(statement)))

        use_name = str(statement.items[2])

        # Extract 'only' list.
        if statement.items[4]:
            only_list = statement.items[4].items
        else:
            only_list = []

        for item in only_list:
            self._arg_name_to_module_name[str(item).lower()] = use_name
Пример #9
0
def write_unicode_file(contents, filename):
    '''Wrapper routine that ensures that a string is encoded as unicode before
    writing to file in both Python 2 and 3.

    :param str contents: string to write to file.
    :param str filename: the name of the file to create.

    :raises InternalError: if an unrecognised Python version is found.

    '''
    import six
    import io

    if six.PY2:
        # In Python 2 a plain string must be encoded as unicode for the call
        # to write() below. unicode() does not exist in Python 3 since all
        # strings are unicode.
        # pylint: disable=undefined-variable
        if not isinstance(contents, unicode):
            contents = unicode(contents, 'utf-8')
        # pylint: enable=undefined-variable
    elif not six.PY3:
        raise InternalError("Unrecognised Python version!")

    encoding = {'encoding': 'utf-8'}
    with io.open(filename, mode='w', **encoding) as file_object:
        file_object.write(contents)
Пример #10
0
def create_var_name(arg_parse_tree):
    '''Creates a valid variable name from an argument that includes
    brackets and potentially dereferences using '%'.

    :param arg_parse_tree: the input argument. Contains braces and \
    potentially dereferencing. e.g. a%b(c)
    :type arg_parse_tree: fparser.two.Fortran2003.Data_Ref
    :returns: a valid variable name as a string
    :rtype: str
    :raises InternalError: if unrecognised fparser content is found.

    '''
    var_name = ""
    tree = arg_parse_tree
    while isinstance(tree, Data_Ref):
        # replace '%' with '_'
        var_name += str(tree.items[0]) + "_"
        tree = tree.items[1]
    if isinstance(tree, Name):
        # add name to the end
        var_name += str(tree)
    elif isinstance(tree, Part_Ref):
        # add name before the brackets to the end
        var_name += str(tree.items[0])
    else:
        raise InternalError(
            "algorithm.py:create_var_name unrecognised structure "
            "'{0}'".format(type(tree)))
    return var_name
Пример #11
0
    def coloured_name(self, colour=True):
        '''
        Returns the display name of this Node, optionally with colour control
        codes (requires that the termcolor package be installed).

        :param bool colour: whether or not to include colour control codes \
                            in the result.

        :returns: the name of this node, optionally with colour control codes.
        :rtype: str
        '''
        if not self._text_name:
            raise NotImplementedError(
                "_text_name is an abstract attribute which needs to be "
                "given a string value in the concrete class '{0}'."
                "".format(type(self).__name__))
        if colour:
            if self._colour is None:
                raise NotImplementedError(
                    "The _colour attribute is abstract so needs to be given "
                    "a string value in the concrete class '{0}'."
                    "".format(type(self).__name__))
            try:
                return colored(self._text_name, self._colour)
            except KeyError as info:
                message = (
                    "The _colour attribute in class '{0}' has been set to a "
                    "colour ('{1}') that is not supported by the termcolor "
                    "package.".format(type(self).__name__, self._colour))
                six.raise_from(InternalError(message), info)
        return self._text_name
Пример #12
0
    def __str__(self):
        '''
        :returns: a description of this array datatype.
        :rtype: str

        :raises InternalError: if an unsupported dimensions type is \
            found.

        '''
        # This import must be placed here to avoid circular
        # dependencies.
        # pylint: disable=import-outside-toplevel
        from psyclone.psyir.nodes import DataNode
        dims = []
        for dimension in self.shape:
            if isinstance(dimension, DataNode):
                dims.append(str(dimension))
            elif isinstance(dimension, ArrayType.Extent):
                dims.append("'{0}'".format(dimension.name))
            else:
                raise InternalError(
                    "ArrayType shape list elements can only be 'DataNode', "
                    "or 'ArrayType.Extent', but found '{0}'."
                    "".format(type(dimension).__name__))
        return ("Array<{0}, shape=[{1}]>".format(self._datatype,
                                                 ", ".join(dims)))
Пример #13
0
    def __str__(self):
        '''
        :returns: a description of this array datatype.
        :rtype: str

        :raises InternalError: if an unsupported dimensions type is \
            found.

        '''
        from psyclone.psyir.symbols import DataSymbol
        dims = []
        for dimension in self.shape:
            if isinstance(dimension, DataSymbol):
                dims.append(dimension.name)
            elif isinstance(dimension, int):
                dims.append(str(dimension))
            elif isinstance(dimension, ArrayType.Extent):
                dims.append("'{0}'".format(dimension.name))
            else:
                raise InternalError(
                    "ArrayType shape list elements can only be 'DataSymbol', "
                    "'int' or 'ArrayType.Extent', but found '{0}'."
                    "".format(type(dimension).__name__))
        return ("Array<{0}, shape=[{1}]>".format(
            self._datatype, ", ".join(dims)))
Пример #14
0
    def _shorten_fs_name(self):
        '''
        Creates short names for any_*_spaces to be used for mangled names
        from the condensed keywords and function space IDs.

        :returns: short name of this function space.
        :rtype: str

        :raises InternalError: if a function space to create the short \
                               name for is not one of 'any_space' or \
                               'any_discontinuous_space' spaces.

        '''
        # Create a start for the short name and check whether the function
        # space is one of any_*_space spaces
        if self._orig_name in FunctionSpace.VALID_ANY_SPACE_NAMES:
            start = "a"
        elif self._orig_name in \
                FunctionSpace.VALID_ANY_DISCONTINUOUS_SPACE_NAMES:
            start = "ad"
        else:
            raise InternalError(
                "_shorten_fs_name: function space '{0}' is not one of "
                "{1} or {2} spaces.".format(
                    self._orig_name, FunctionSpace.VALID_ANY_SPACE_NAMES,
                    FunctionSpace.VALID_ANY_DISCONTINUOUS_SPACE_NAMES))

        # Split name string to find any_*_space ID and create a short name as
        # "<start>" + "spc" + "ID"
        fslist = self._orig_name.split("_")
        self._short_name = start + "spc" + fslist[-1]
        return self._short_name
Пример #15
0
 def __init__(self, form, text, varname=None):
     self._form = form
     self._text = text
     self._varname = varname
     if form not in Arg.form_options:
         raise InternalError(
             "algorithm.py:Alg:__init__: Unknown arg type provided. "
             "Expected one of {0} but found "
             "'{1}'.".format(str(Arg.form_options), form))
Пример #16
0
 def __init__(self, variable):
     if isinstance(variable, (str, six.text_type)):
         # str() required for python2 unicode support
         self._signature = (str(variable), )
     elif isinstance(variable, tuple):
         self._signature = variable
     else:
         raise InternalError("Got unexpected type '{0}' in Signature "
                             "constructor".format(type(variable).__name__))
Пример #17
0
    def change_read_to_write(self):
        '''This function is only used when analysing an assignment statement.
        The LHS has first all variables identified, which will be READ.
        This function is then called to change the assigned-to variable
        on the LHS to from READ to WRITE. Since the LHS is stored in a separate
        SingleVariableAccessInfo class, it is guaranteed that there is only
        one entry for the variable.
        '''
        if len(self._accesses) != 1:
            raise InternalError("Variable '{0}' had {1} accesses listed, "
                                "not one in change_read_to_write.".format(
                                    self._signature, len(self._accesses)))

        if self._accesses[0].access_type != AccessType.READ:
            raise InternalError("Trying to change variable '{0}' to 'WRITE' "
                                "which does not have 'READ' access.".format(
                                    self._signature))

        self._accesses[0].change_read_to_write()
Пример #18
0
 def __init__(self, ast):
     # pylint: disable=super-init-not-called
     names = walk(ast.content, Fortran2003.Name)
     # The name of the program unit will be the first in the list
     if not names:
         raise InternalError("Found no names in supplied Fortran - should "
                             "be impossible!")
     self._name = str(names[0]) + "_psy"
     self._invokes = NemoInvokes(ast)
     self._ast = ast
Пример #19
0
def create_var_name(arg_parse_tree):
    '''Creates a valid variable name from an argument that optionally
    includes brackets and potentially dereferences using '%'.

    :param arg_parse_tree: the input argument. Contains braces and \
        potentially dereferencing. e.g. a%b(c).
    :type arg_parse_tree: :py:class:`fparser.two.Fortran2003.Name` or \
        :py:class:`fparser.two.Fortran2003.Data_Ref` or \
        :py:class:`fparser.two.Fortran2003.Part_Ref` or \
        :py:class:`fparser.two.Fortran2003.Proc_Component_Ref`

    :returns: a valid variable name.
    :rtype: str

    :raises InternalError: if unrecognised fparser content is found.

    '''
    tree = arg_parse_tree
    if isinstance(tree, Name):
        return str(tree)
    if isinstance(tree, Part_Ref):
        return str(tree.items[0])
    if isinstance(tree, Proc_Component_Ref):
        # Proc_Component_Ref is of the form 'variable %
        # proc-name'. Its RHS (proc-name) is always a Name but its
        # LHS (variable) could be more complex, so call the function
        # again for the LHS.
        return "{0}_{1}".format(create_var_name(tree.items[0]), tree.items[2])
    if isinstance(tree, Data_Ref):
        component_names = []
        for item in tree.items:
            if isinstance(item, (Data_Ref, Part_Ref)):
                component_names.append(str(item.items[0]))
            elif isinstance(item, Name):
                component_names.append(str(item))
            else:
                raise InternalError(
                    "algorithm.py:create_var_name unrecognised structure "
                    "'{0}' in '{1}'.".format(type(item), type(tree)))
        return "_".join(component_names)
    raise InternalError("algorithm.py:create_var_name unrecognised structure "
                        "'{0}'".format(type(tree)))
Пример #20
0
 def __init__(self, parent=None, annotations=None):
     super(IfBlock, self).__init__(parent=parent)
     if annotations:
         for annotation in annotations:
             if annotation in IfBlock.valid_annotations:
                 self._annotations.append(annotation)
             else:
                 raise InternalError(
                     "IfBlock with unrecognized annotation '{0}', valid "
                     "annotations are: {1}.".format(
                         annotation, IfBlock.valid_annotations))
Пример #21
0
    def gen_code(self, parent):
        '''This method must not be called for NEMO, since the actual
        kernels are inlined.

        :param parent: The parent of this kernel call in the f2pygen AST.
        :type parent: :py:calls:`psyclone.f2pygen.LoopGen`

        :raises InternalError: if this function is called.
        '''
        raise InternalError("NEMO kernels are assumed to be in-lined by "
                            "default therefore the gen_code method should not "
                            "have been called.")
Пример #22
0
 def __init__(self, form, text, varname=None, datatype=None):
     self._form = form
     self._text = text
     self._varname = varname
     if form not in Arg.form_options:
         raise InternalError(
             "algorithm.py:Alg:__init__: Unknown arg type provided. "
             "Expected one of {0} but found "
             "'{1}'.".format(str(Arg.form_options), form))
     # A tuple containing information about the datatype and
     # precision of this argument, or None if there is none.
     self._datatype = datatype
Пример #23
0
    def condition(self):
        ''' Return the PSyIR Node representing the conditional expression
        of this IfBlock.

        :returns: IfBlock conditional expression.
        :rtype: :py:class:`psyclone.psyir.nodes.Node`
        :raises InternalError: If the IfBlock node does not have the correct \
            number of children.
        '''
        if len(self.children) < 2:
            raise InternalError(
                "IfBlock malformed or incomplete. It should have at least 2 "
                "children, but found {0}.".format(len(self.children)))
        return self._children[0]
Пример #24
0
    def nlayers_positions(self):
        ''':returns: the position(s) in the argument list of the \
            variable(s) that passes the number of layers. The generate \
            method must be called first.
        :rtype: list of int.

        :raises InternalError: if the generate() method has not been called.

        '''
        if not self._generate_called:
            raise InternalError(
                "KernCallArgList: the generate() method should be called "
                "before the nlayers_positions() method")
        return self._nlayers_positions
Пример #25
0
    def rhs(self):
        '''
        :returns: the child node representing the Right-Hand Side of the \
            assignment.
        :rtype: :py:class:`psyclone.psyir.nodes.Node`

        :raises InternalError: Node has fewer children than expected.
        '''
        if len(self._children) < 2:
            raise InternalError(
                "Assignment '{0}' malformed or incomplete. It "
                "needs at least 2 children to have a rhs.".format(repr(self)))

        return self._children[1]
Пример #26
0
    def change_read_to_write(self):
        '''This changes the access mode from READ to WRITE.
        This is used for processing assignment statements,
        where the LHS is first considered to be READ,
        and which is then changed to be WRITE.

        :raises InternalError: if the variable originally does not have\
            READ access.

        '''
        if self._access_type != AccessType.READ:
            raise InternalError("Trying to change variable to 'WRITE' "
                                "which does not have 'READ' access.")
        self._access_type = AccessType.WRITE
Пример #27
0
    def member(self):
        '''
        :returns: the member of the structure that this reference is to.
        :rtype: :py:class:`psyclone.psyir.nodes.Member`

        :raises InternalError: if the first child of this node is not an \
                               instance of Member.
        '''
        if not self.children or not isinstance(self.children[0], Member):
            raise InternalError(
                "{0} malformed or incomplete. It must have a single child "
                "that must be a (sub-class of) Member, but found: {1}".format(
                    type(self).__name__, self.children))
        return self.children[0]
Пример #28
0
    def if_body(self):
        ''' Return the Schedule executed when the IfBlock evaluates to True.

        :returns: Schedule to be executed when IfBlock evaluates to True.
        :rtype: :py:class:`psyclone.psyir.nodes.Schedule`
        :raises InternalError: If the IfBlock node does not have the correct \
            number of children.
        '''
        if len(self.children) < 2:
            raise InternalError(
                "IfBlock malformed or incomplete. It should have at least 2 "
                "children, but found {0}.".format(len(self.children)))

        return self._children[1]
Пример #29
0
    def _check_completeness(self):
        ''' Check that the Loop has 4 children and the 4th is a Schedule.

        :raises InternalError: If the loop does not have 4 children or the
            4th one is not a Schedule
        '''
        # We cannot just do str(self) in this routine we can end up being
        # called as a result of str(self) higher up the call stack
        # (because loop bounds are evaluated dynamically).
        if len(self.children) < 4:
            raise InternalError(
                "Loop is incomplete. It should have exactly 4 "
                "children, but found loop with '{0}'.".format(", ".join(
                    [str(child) for child in self.children])))
Пример #30
0
    def member(self):
        '''
        :returns: the member of the structure that is being accessed.
        :rtype: (sub-class of) :py:class:`psyclone.psyir.nodes.Member`

        :raises InternalError: if the first child of this node is not an \
                               instance of Member.
        '''
        if not isinstance(self.children[0], Member):
            raise InternalError(
                "{0} malformed or incomplete. The first child "
                "must be an instance of Member, but found '{1}'".format(
                    type(self).__name__, type(self.children[0]).__name__))
        return self.children[0]