コード例 #1
0
    def validate(self, node, options=None):
        '''Validate the supplied PSyIR tree.

        :param node: a PSyIR node that is the root of a PSyIR tree.
        :type node: :py:class:`psyclone.psyir.node.Routine` or \
            :py:class:`psyclone.psyir.node.Container`
        :param options: a dictionary with options for transformations.
        :type options: dictionary of string:values or None

        :raises TransformationError: if the supplied node argument is \
            not a Routine or a Container.
        :raises TransformationError: if the supplied node argument has \
            a parent.

        '''
        if not isinstance(node, (Routine, Container)):
            raise TransformationError(
                "Error in {0} transformation. The supplied call argument "
                "should be a Routine or Container node but found '{1}'."
                "".format(self.name,
                          type(node).__name__))
        if node.parent:
            raise TransformationError(
                "Error in {0} transformation. The supplied node should be the "
                "root of a PSyIR tree but this node has a parent."
                "".format(self.name))
コード例 #2
0
    def apply(self, node1, node2, options=None):
        ''' Fuses two `psyclone.gocean1p0.GOLoop` loops after performing
        validity checks by calling :py:meth:`LoopFuseTrans.apply` method
        of the base class.

        :param node1: the first Node representing a GOLoop.
        :type node1: :py:class:`psyclone.gocean1p0.GOLoop`
        :param node2: the second Node representing a GOLoop.
        :type node2: :py:class:`psyclone.gocean1p0.GOLoop`
        :param options: a dictionary with options for transformations.
        :type options: dictionary of string:values or None

        :returns: two-tuple of the modified Schedule and a record of \
                  the transformation.
        :rtype: (:py:class:`psyclone.psyir.nodes.Schedule`, \
                 :py:class:`psyclone.undoredo.Memento`)

        :raises TransformationError: if the supplied loops are over \
                                     different grid-point types.
        :raises TransformationError: if there is an unexpected exception.
        '''

        # Validate first
        self.validate(node1, node2, options=options)

        # Now check for GOcean-specific constraints before applying
        # the transformation
        try:
            return LoopFuseTrans.apply(self, node1, node2, options)
        except Exception as err:
            raise TransformationError(
                "Error in {0} transformation. Unexpected exception: {1}".
                format(self.name, err))
コード例 #3
0
ファイル: nemo_loop_fuse.py プロジェクト: hiker/PSyclone
    def validate_written_scalar(var_info1, var_info2):
        '''Validates if the accesses to a scalar that is at least written once
        allows loop fusion. The accesses of the variable is contained in the
        two parameters (which also include the name of the variable).

        :param var_info1: access information for variable in the first loop.
        :type var_info1: :py:class:`psyclone.core.var_info.VariableInfo`
        :param var_info2: access information for variable in the second loop.
        :type var_info2: :py:class:`psyclone.core.var_info.VariableInfo`

        :raises TransformationError: a scalar variable is written in one \
            loop, but only read in the other.

        '''
        # If a scalar variable is first written in both loops, that pattern
        # is typically ok. Example:
        # - inner loops (loop variable is written then read,
        # - a=sqrt(j); b(j)=sin(a)*cos(a) - a scalar variable as 'constant'
        # TODO #641: atm the variable access information has no details
        # about a conditional access, so the test below could result in
        # incorrectly allowing fusion. But the test is essential for many
        # much more typical use cases (especially inner loops).
        if var_info1[0].access_type == AccessType.WRITE and \
                var_info2[0].access_type == AccessType.WRITE:
            return

        raise TransformationError(
            "Scalar variable '{0}' is written in one loop, but only read "
            "in the other loop.".format(var_info1.var_name))
コード例 #4
0
    def validate(self, node_list, options=None):
        ''' Perform Dynamo0.3 API specific validation checks before applying
        the transformation.

        :param node_list: the list of Node(s) we are checking.
        :type node_list: list of :py:class:`psyclone.psyir.nodes.Node`
        :param options: a dictionary with options for transformations.
        :type options: dictionary of string:values or None

        :raises TransformationError: if transformation is applied to a Loop \
                                     over cells in a colour without its \
                                     parent Loop over colours.
        '''

        # First check constraints on Nodes in the node_list inherited from
        # the parent classes (ExtractTrans and RegionTrans)
        super(LFRicExtractTrans, self).validate(node_list, options)

        # Check LFRicExtractTrans specific constraints
        for node in node_list:

            # Check that ExtractNode is not inserted between a Loop
            # over colours and a Loop over cells in a colour when
            # colouring is applied.
            ancestor = node.ancestor(DynLoop)
            if ancestor and ancestor.loop_type == 'colours':
                raise TransformationError(
                    "Error in {0} for Dynamo0.3 API: Extraction of a Loop "
                    "over cells in a colour without its ancestor Loop over "
                    "colours is not allowed.".format(str(self.name)))
コード例 #5
0
    def _validate_fp2_node(self, fp2_node):
        '''Specialisation of the fparser2 node validation routine to
        additionally validate named arguments, which are specific to
        the LFRic API.

        :param fp2_node: an fparser2 Structure Constructor or Actual \
            Arg Spec node.
        :type fp2_node: \
            :py:class:`fparser.two.Fortran2003.Structure_Constructor` or \
            :py:class:`fparser.two.Fortran2003.Actual_Arg_Spec

        :raises TransformationError: if the named argument is not in \
            the expected form.
        :raises TransformationError: if more than one named argument \
            is found.
        :raises TransformationError: if the fparser2 node is not the \
            expected type.

        '''
        if isinstance(fp2_node, Structure_Constructor):
            pass
        elif isinstance(fp2_node, Actual_Arg_Spec):
            if not (isinstance(fp2_node.children[0], Name)
                    and fp2_node.children[0].string.lower() == "name" and
                    isinstance(fp2_node.children[1], Char_Literal_Constant)):
                raise TransformationError(
                    "Error in {0} transformation. If there is a named "
                    "argument, it must take the form name='str', but found "
                    "'{1}'.".format(self.name, str(fp2_node)))
            if self._call_description:
                raise TransformationError(
                    "Error in {0} transformation. There should be at most one "
                    "named argument in an invoke, but there are at least two: "
                    "{1} and {2}.".format(self.name, self._call_description,
                                          fp2_node.children[1].string))
            self._call_description = fp2_node.children[1].string
        else:
            raise TransformationError(
                "Error in {0} transformation. Expecting an algorithm invoke "
                "codeblock to contain either Structure-Constructor or "
                "actual-arg-spec, but found '{1}'."
                "".format(self.name,
                          type(fp2_node).__name__))
コード例 #6
0
ファイル: invokecall_trans.py プロジェクト: hiker/PSyclone
    def validate(self, call, options=None):
        '''Validate the call argument.

        :param call: a PSyIR call node capturing an invoke call in \
            generic PSyIR.
        :type call: :py:class:`psyclone.psyir.nodes.Call`
        :param options: a dictionary with options for transformations.
        :type options: dictionary of string:values or None

        :raises TransformationError: if the supplied call argument is \
            not a PSyIR Call node.
        :raises TransformationError: if the supplied call argument \
            does not have the expected name which would identify it as an \
            invoke call.
        :raises TransformationError: if the invoke arguments are not a \
            PSyIR ArrayReference or CodeBlock.

        '''
        if not isinstance(call, Call):
            raise TransformationError(
                "Error in {0} transformation. The supplied call argument "
                "should be a `Call` node but found '{1}'."
                "".format(self.name,
                          type(call).__name__))
        if not call.routine.name.lower() == "invoke":
            raise TransformationError(
                "Error in {0} transformation. The supplied call argument "
                "should be a `Call` node with name 'invoke' but found '{1}'."
                "".format(self.name, call.routine.name))
        for arg in call.children:
            if isinstance(arg, ArrayReference):
                pass
            elif isinstance(arg, CodeBlock):
                for fp2_node in arg._fp2_nodes:
                    self._validate_fp2_node(fp2_node)
            else:
                raise TransformationError(
                    "Error in {0} transformation. The arguments to this "
                    "invoke call are expected to be a CodeBlock or an "
                    "ArrayReference, but found '{1}'."
                    "".format(self.name,
                              type(arg).__name__))
コード例 #7
0
    def validate(self, node1, node2, options=None):
        '''Checks if it is valid to apply the GOceanLoopFuseTrans
        transform. It ensures that the fused loops are over
        the same grid-point types, before calling the normal
        LoopFuseTrans validation function.

        :param node1: the first Node representing a GOLoop.
        :type node1: :py:class:`psyclone.gocean1p0.GOLoop`
        :param node2: the second Node representing a GOLoop.
        :type node2: :py:class:`psyclone.gocean1p0.GOLoop`
        :param options: a dictionary with options for transformations.
        :type options: dictionary of string:values or None

        :raises TransformationError: if the supplied loops are over \
                                     different grid-point types.

        :raises TransformationError: if invalid parameters are passed in.

        '''

        # Either both nodes are gocean1.0 loop nodes, or both
        # nodes are gocean0.1 loop nodes, otherwise raise an exception:
        if not ((isinstance(node1, psyclone.gocean0p1.GOLoop)
                 and isinstance(node2, psyclone.gocean0p1.GOLoop)) or
                (isinstance(node1, psyclone.gocean1p0.GOLoop)
                 and isinstance(node2, psyclone.gocean1p0.GOLoop))):
            raise TransformationError("Error in {0} transformation. "
                                      "Both nodes must be of the same "
                                      "GOLoop class.".format(self.name))

        super(GOceanLoopFuseTrans, self).validate(node1,
                                                  node2,
                                                  options=options)

        if node1.field_space != node2.field_space:
            raise TransformationError(
                "Error in {0} transformation. Cannot "
                "fuse loops that are over different grid-point types: "
                "{1} {2}".format(self.name, node1.field_space,
                                 node2.field_space))
コード例 #8
0
ファイル: matmul2code_trans.py プロジェクト: stfc/PSyclone
def _get_array_bound(array, index):
    '''A utility function that returns the appropriate loop bounds (lower,
    upper and step) for a particular index of an array reference. At
    the moment all entries for the index are assumed to be accessed.
    If the array symbol is declared with known bounds (an integer or a
    symbol) then these bound values are used. If the size is unknown
    (a deferred or attribute type) then the LBOUND and UBOUND PSyIR
    nodes are used.

    :param array: the reference that we are interested in.
    :type array: :py:class:`psyir.nodes.Reference`
    :param int index: the (array) reference index that we are \
        interested in.
    :returns: the loop bounds for this array index.
    :rtype: (Literal, Literal, Literal) or \
        (BinaryOperation, BinaryOperation, Literal)

    :raises TransformationError: if the shape of the array's symbol is \
        not supported.

    '''
    # Added import here to avoid circular dependencies.
    # pylint: disable=import-outside-toplevel
    from psyclone.psyir.transformations import TransformationError

    my_dim = array.symbol.shape[index]
    if isinstance(my_dim, DataNode):
        lower_bound = Literal("1", INTEGER_TYPE)
        upper_bound = my_dim
    elif isinstance(my_dim, DataSymbol):
        lower_bound = Literal("1", INTEGER_TYPE)
        upper_bound = Reference(my_dim)
    elif my_dim in [ArrayType.Extent.DEFERRED, ArrayType.Extent.ATTRIBUTE]:
        lower_bound = BinaryOperation.create(BinaryOperation.Operator.LBOUND,
                                             Reference(array.symbol),
                                             Literal(str(index), INTEGER_TYPE))
        upper_bound = BinaryOperation.create(BinaryOperation.Operator.UBOUND,
                                             Reference(array.symbol),
                                             Literal(str(index), INTEGER_TYPE))
    else:
        raise TransformationError(
            "Unsupported index type '{0}' found for dimension {1} of array "
            "'{2}'.".format(type(my_dim).__name__, index + 1, array.name))
    step = Literal("1", INTEGER_TYPE)
    return (lower_bound, upper_bound, step)
    def validate(self, node, options=None):
        '''Ensure that it is valid to apply this transformation to the
        supplied node.

        :param node: the node to validate.
        :type node: :py:class:`psyclone.gocean1p0.GOKern`
        :param options: a dictionary with options for transformations.
        :type options: dict of string:values or None

        :raises TransformationError: if the node is not a GOKern.

        '''
        if not isinstance(node, GOKern):
            raise TransformationError(
                "Error in {0} transformation. This transformation can only be "
                "applied to 'GOKern' nodes, but found '{1}'."
                "".format(self.name,
                          type(node).__name__))
コード例 #10
0
ファイル: invokecall_trans.py プロジェクト: hiker/PSyclone
    def _validate_fp2_node(self, fp2_node):
        '''Separate validation routine for an fparser2 node within a code
        block. This is separated to make it simpler to subclass.

        :param fp2_node: an fparser2 Structure Constructor node.
        :type fp2_node: \
            :py:class:`fparser.two.Fortran2003.Structure_Constructor`

        :raises TransformationError: if the fparser2 node is not a \
            Structure Constructor.

        '''
        if not isinstance(fp2_node, Structure_Constructor):
            raise TransformationError(
                "Error in {0} transformation. The supplied call "
                "argument contains a CodeBlock with content "
                "({1}) which is not a StructureConstructor."
                "".format(self.name,
                          type(fp2_node).__name__))
コード例 #11
0
ファイル: gocean_extract_trans.py プロジェクト: stfc/PSyclone
    def validate(self, node_list, options=None):
        ''' Perform GOcean1.0 API specific validation checks before applying
        the transformation.

        :param node_list: the list of Node(s) we are checking.
        :type node_list: list of :py:class:`psyclone.psyir.nodes.Node`
        :param options: a dictionary with options for transformations.
        :type options: dictionary of string:values or None
        :param bool options["create_driver"]: whether or not to create a \
            driver program at code-generation time. If set, the driver will \
            be created in the current working directory with the name \
            "driver-MODULE-REGION.f90" where MODULE and REGION will be the \
            corresponding values for this region. This flag is forwarded to \
            the GoceanExtractNode. Its default value is False.
        :param (str,str) options["region_name"]: an optional name to \
            use for this data-extraction region, provided as a 2-tuple \
            containing a module name followed by a local name. The pair of \
            strings should uniquely identify a region unless aggregate \
            information is required (and is supported by the runtime \
            library). This option is forwarded to the PSyDataNode (where it \
            changes the region names) and to the GOceanExtractNode (where it \
            changes the name of the created output files and the name of the \
            driver program).

        :raises TransformationError: if transformation is applied to an \
            inner Loop without its parent outer Loop.
        '''

        # First check constraints on Nodes in the node_list inherited from
        # the parent classes (ExtractTrans and RegionTrans)
        super(GOceanExtractTrans, self).validate(node_list, options)

        # Check GOceanExtractTrans specific constraints
        for node in node_list:

            # Check that ExtractNode is not inserted between an inner
            # and an outer Loop.
            ancestor = node.ancestor(GOLoop)
            if ancestor and ancestor.loop_type == 'outer':
                raise TransformationError(
                    "Error in {0}: Application to an "
                    "inner Loop without its ancestor outer Loop is not "
                    "allowed.".format(str(self.name)))
コード例 #12
0
    def validate(self, node1, node2, options=None):
        ''' Performs various checks to ensure that it is valid to apply
        the LFRicLoopFuseTrans transformation to the supplied loops.

        :param node1: the first Loop to fuse.
        :type node1: :py:class:`psyclone.dynamo0p3.DynLoop`
        :param node2: the second Loop to fuse.
        :type node2: :py:class:`psyclone.dynamo0p3.DynLoop`
        :param options: a dictionary with options for transformations.
        :type options: dictionary of string:values or None
        :param bool options["same_space"]: this optional flag, set to `True`, \
            asserts that an unknown iteration space (i.e. `ANY_SPACE`) \
            matches the other iteration space. This is set at the user's own \
            risk. If both iteration spaces are discontinuous the loops can be \
            fused without having to use the `same_space` flag.

        :raises TransformationError: if either of the supplied loops contains \
                                     an inter-grid kernel.
        :raises TransformationError: if one or both function spaces have \
                                     invalid names.
        :raises TransformationError: if the `same_space` flag was set, but \
                                     does not apply because neither field \
                                     is on `ANY_SPACE` or the spaces are not \
                                     the same.
        :raises TransformationError: if one or more of the iteration spaces \
                                     is unknown (`ANY_SPACE`) and the \
                                     `same_space` flag is not set to `True`.
        :raises TransformationError: if the loops are over different spaces \
                                     that are not both discontinuous and \
                                     the loops both iterate over cells.
        :raises TransformationError: if the loops' upper bound names are \
                                     not the same.
        :raises TransformationError: if the halo-depth indices of two loops \
                                     are not the same.
        :raises TransformationError: if each loop already contains a reduction.
        :raises TransformationError: if the first loop has a reduction and \
                                     the second loop reads the result of \
                                     the reduction.
        '''
        # pylint: disable=too-many-locals,too-many-branches
        # Call the parent class validation first

        if not options:
            options = {}
        same_space = options.get("same_space", False)
        if same_space and not isinstance(same_space, bool):
            raise TransformationError(
                "Error in {0} transformation: The value of the 'same_space' "
                "flag must be either bool or None type, but the type of "
                "flag provided was '{1}'.".
                format(self.name, type(same_space).__name__))
        super(LFRicLoopFuseTrans, self).validate(node1, node2,
                                                 options=options)
        # Now test for Dynamo-specific constraints

        # 1) Check that we don't have an inter-grid kernel
        check_intergrid(node1)
        check_intergrid(node2)

        # 2) Check function space names
        node1_fs_name = node1.field_space.orig_name
        node2_fs_name = node2.field_space.orig_name
        # 2.1) Check that both function spaces are valid
        const = LFRicConstants()
        if not (node1_fs_name in const.VALID_FUNCTION_SPACE_NAMES and
                node2_fs_name in const.VALID_FUNCTION_SPACE_NAMES):
            raise TransformationError(
                "Error in {0} transformation: One or both function "
                "spaces '{1}' and '{2}' have invalid names.".
                format(self.name, node1_fs_name, node2_fs_name))
        # Check whether any of the spaces is ANY_SPACE. Loop fusion over
        # ANY_SPACE is allowed only when the 'same_space' flag is set
        node_on_any_space = node1_fs_name in \
            const.VALID_ANY_SPACE_NAMES or \
            node2_fs_name in const.VALID_ANY_SPACE_NAMES
        # 2.2) If 'same_space' is true check that both function spaces are
        # the same or that at least one of the nodes is on ANY_SPACE. The
        # former case is convenient when loop fusion is applied generically.

        if same_space:
            if node1_fs_name == node2_fs_name:
                pass
            elif not node_on_any_space:
                raise TransformationError(
                    "Error in {0} transformation: The 'same_space' "
                    "flag was set, but does not apply because "
                    "neither field is on 'ANY_SPACE'.".format(self))
        # 2.3) If 'same_space' is not True then make further checks
        else:
            # 2.3.1) Check whether one or more of the function spaces
            # is ANY_SPACE without the 'same_space' flag
            if node_on_any_space:
                raise TransformationError(
                    "Error in {0} transformation: One or more of the "
                    "iteration spaces is unknown ('ANY_SPACE') so loop "
                    "fusion might be invalid. If you know the spaces "
                    "are the same then please set the 'same_space' "
                    "optional argument to 'True'.".format(self.name))
            # 2.3.2) Check whether specific function spaces are the
            # same. If they are not, the loop fusion is still possible
            # but only when both function spaces are discontinuous
            # (w3, w2v, wtheta or any_discontinuous_space) and the upper
            # loop bounds are the same (checked further below).
            if node1_fs_name != node2_fs_name:
                if not (node1_fs_name in
                        const.VALID_DISCONTINUOUS_NAMES and
                        node2_fs_name in
                        const.VALID_DISCONTINUOUS_NAMES):
                    raise TransformationError(
                        "Error in {0} transformation: Cannot fuse loops "
                        "that are over different spaces '{1}' and '{2}' "
                        "unless they are both discontinuous.".
                        format(self.name, node1_fs_name,
                               node2_fs_name))

        # 3) Check upper loop bounds
        if node1.upper_bound_name != node2.upper_bound_name:
            raise TransformationError(
                "Error in {0} transformation: The upper bound names "
                "are not the same. Found '{1}' and '{2}'.".
                format(self.name, node1.upper_bound_name,
                       node2.upper_bound_name))

        # 4) Check halo depths
        if node1.upper_bound_halo_depth != node2.upper_bound_halo_depth:
            raise TransformationError(
                "Error in {0} transformation: The halo-depth indices "
                "are not the same. Found '{1}' and '{2}'.".
                format(self.name, node1.upper_bound_halo_depth,
                       node2.upper_bound_halo_depth))

        # 5) Check for reductions
        arg_types = const.VALID_SCALAR_NAMES
        all_reductions = AccessType.get_valid_reduction_modes()
        node1_red_args = node1.args_filter(arg_types=arg_types,
                                           arg_accesses=all_reductions)
        node2_red_args = node2.args_filter(arg_types=arg_types,
                                           arg_accesses=all_reductions)

        if node1_red_args and node2_red_args:
            raise TransformationError(
                "Error in {0} transformation: Cannot fuse loops "
                "when each loop already contains a reduction.".
                format(self.name))
        if node1_red_args:
            for reduction_arg in node1_red_args:
                other_args = node2.args_filter()
                for arg in other_args:
                    if reduction_arg.name == arg.name:
                        raise TransformationError(
                            "Error in {0} transformation: Cannot fuse "
                            "loops as the first loop has a reduction "
                            "and the second loop reads the result of "
                            "the reduction.".format(self.name))
コード例 #13
0
ファイル: matmul2code_trans.py プロジェクト: stfc/PSyclone
    def validate(self, node, options=None):
        '''Perform checks to ensure that it is valid to apply the
        Matmul2CodeTran transformation to the supplied node.

        :param node: the node that is being checked.
        :type node: :py:class:`psyclone.psyir.nodes.BinaryOperation`
        :param options: a dictionary with options for transformations.
        :type options: dictionary of string:values or None

        :raises TransformationError: if the node argument is not the \
            expected type.
        :raises TransformationError: if the parent of the MATMUL \
            operation is not an assignment.
        :raises TransformationError: if the matmul arguments are not in \
            the required form.

        '''
        # pylint: disable=too-many-branches

        # Import here to avoid circular dependencies.
        # pylint: disable=import-outside-toplevel
        from psyclone.psyir.transformations import TransformationError

        super(Matmul2CodeTrans, self).validate(node, options)

        # Check the matmul is the only code on the rhs of an assignment
        # i.e. ... = matmul(a,b)
        if not isinstance(node.parent, Assignment):
            raise TransformationError(
                "Matmul2CodeTrans only supports the transformation of a "
                "MATMUL operation when it is the sole operation on the rhs "
                "of an assignment.")

        matrix = node.children[0]
        vector = node.children[1]
        # The children of matvec should be References
        if not (isinstance(matrix, Reference)
                and isinstance(vector, Reference)):
            raise TransformationError(
                "Expected children of a MATMUL BinaryOperation to be "
                "references, but found '{0}', '{1}'."
                "".format(type(matrix).__name__,
                          type(vector).__name__))

        # The children of matvec should be References to arrays
        if not (matrix.symbol.shape or vector.symbol.shape):
            raise TransformationError(
                "Expected children of a MATMUL BinaryOperation to be "
                "references to arrays, but found '{0}', '{1}'."
                "".format(
                    type(matrix.symbol).__name__,
                    type(vector.symbol).__name__))

        # The first child (the matrix) should be declared as an array
        # with at least 2 dimensions
        if len(matrix.symbol.shape) < 2:
            raise TransformationError(
                "Expected 1st child of a MATMUL BinaryOperation to be "
                "a matrix with at least 2 dimensions, but found '{0}'."
                "".format(len(matrix.symbol.shape)))
        if len(matrix.symbol.shape) > 2 and not matrix.children:
            # If the matrix has no children then it is a reference. If
            # it is a reference then the number of arguments must be
            # 2.
            raise TransformationError(
                "Expected 1st child of a MATMUL BinaryOperation to have 2 "
                "dimensions, but found '{0}'."
                "".format(len(matrix.symbol.shape)))
        if len(matrix.symbol.shape) == 2 and not matrix.children:
            # If the matrix only has 2 dimensions and all of its data is
            # used in the matrix vector multiply then the reference does
            # not need to supply any dimension information.
            pass
        else:
            # There should be one index per dimension. This is enforced
            # by the array create method so is not tested here.

            # The first two indices should be ranges. This is a
            # limitation of this transformation, not the PSyIR, as it
            # would be valid to have any two indices being
            # ranges. Further, this transformation is currently
            # limited to Ranges which specify the full extent of the
            # dimension.
            if not (matrix.is_full_range(0) and matrix.is_full_range(1)):
                raise NotImplementedError(
                    "To use matmul2code_trans on matmul, indices 0 and 1 of "
                    "the 1st (matrix) argument '{0}' must be full ranges."
                    "".format(matrix.name))

            if len(matrix.children) > 2:
                # The 3rd index and onwards must not be ranges.
                for (count, index) in enumerate(matrix.children[2:]):
                    if isinstance(index, Range):
                        raise NotImplementedError(
                            "To use matmul2code_trans on matmul, only the "
                            "first two indices of the 1st (matrix) argument "
                            "are permitted to be Ranges but found {0} at "
                            "index {1}.".format(
                                type(index).__name__, count + 2))

        if len(vector.symbol.shape) > 1 and not vector.children:
            # If the vector has no children then it is a reference. If
            # it is a reference then the number of arguments must be
            # 1.
            raise TransformationError(
                "Expected 2nd child of a MATMUL BinaryOperation to have 1 "
                "dimension, but found '{0}'."
                "".format(len(vector.symbol.shape)))
        if len(vector.symbol.shape) == 1 and not vector.children:
            # If the vector only has 1 dimension and all of its data is
            # used in the matrix vector multiply then the reference does
            # not need to supply any dimension information.
            pass
        else:
            # There should be one index per dimension. This is enforced
            # by the array create method so is not tested here.

            # The first index should be a range. This
            # transformation is currently limited to Ranges which
            # specify the full extent of the dimension.
            if not vector.is_full_range(0):
                raise NotImplementedError(
                    "To use matmul2code_trans on matmul, index 0 of the 2nd "
                    "(vector) argument '{0}' must be a full range."
                    "".format(matrix.name))
            if len(vector.children) > 1:
                # The 2nd index and onwards must not be ranges.
                for (count, index) in enumerate(vector.children[1:]):
                    if isinstance(index, Range):
                        raise NotImplementedError(
                            "To use matmul2code_trans on matmul, only the "
                            "first index of the 2nd (vector) argument is "
                            "permitted to be a Range but found {0} at index "
                            "{1}.".format(type(index).__name__, count + 1))
コード例 #14
0
ファイル: nemo_loop_fuse.py プロジェクト: hiker/PSyclone
    def validate(self, node1, node2, options=None):
        ''' Perform NEMO API specific validation checks before applying
        the transformation.

        :param node1: the first Node that is being checked.
        :type node1: :py:class:`psyclone.psyir.nodes.Node`
        :param node2: the second Node that is being checked.
        :type node2: :py:class:`psyclone.psyir.nodes.Node`
        :param options: a dict with options for transformations.
        :type options: dict of string:values or None

        :raises TransformationError: if the lower or upper loop boundaries \
            are not the same.
        :raises TransformationError: if the loop step size is not the same.
        :raises TransformationError: if the loop variables are not the same.

        '''
        # pylint: disable=too-many-locals
        # First check constraints on the nodes inherited from the parent
        # LoopFuseTrans:
        super(NemoLoopFuseTrans, self).validate(node1, node2, options)

        if not node1.start_expr.math_equal(node2.start_expr):
            raise TransformationError("Lower loop bounds must be identical, "
                                      "but are '{0}'' and '{1}'".format(
                                          node1.start_expr, node2.start_expr))
        if not node1.stop_expr.math_equal(node2.stop_expr):
            raise TransformationError("Upper loop bounds must be identical, "
                                      "but are '{0}'' and '{1}'".format(
                                          node1.stop_expr, node2.stop_expr))
        if not node1.step_expr.math_equal(node2.step_expr):
            raise TransformationError("Step size in loops must be identical, "
                                      "but are '{0}'' and '{1}'".format(
                                          node1.step_expr, node2.step_expr))
        loop_var1 = node1.variable
        loop_var2 = node2.variable

        if loop_var1 != loop_var2:
            raise TransformationError("Loop variables must be the same, "
                                      "but are '{0}' and '{1}'".format(
                                          loop_var1.name, loop_var2.name))
        vars1 = VariablesAccessInfo(node1)
        vars2 = VariablesAccessInfo(node2)

        # Get all variables that occur in both loops. A variable
        # that is only in one loop is not affected by fusion.
        all_vars = set(vars1).intersection(vars2)
        symbol_table = node1.scope.symbol_table

        for signature in all_vars:
            var_name = str(signature)
            # Ignore the loop variable
            if var_name == loop_var1.name:
                continue
            var_info1 = vars1[signature]
            var_info2 = vars2[signature]

            # Variables that are only read in both loops can always be fused
            if var_info1.is_read_only() and var_info2.is_read_only():
                continue

            # TODO #1213 - try to find symbol in case of 'wildcard' imports.
            try:
                # Find the symbol for this variable - the lookup function
                # checks automatically in outer scopes.
                symbol = symbol_table.lookup(var_name)
                if isinstance(symbol, DataSymbol):
                    is_array = symbol.is_array
                else:
                    # This typically indicates a symbol is used that we do
                    # not have detailed information for, e.g. based on a
                    # generic 'use some_mod' statement. In this case use
                    # the information based on the access pattern, which
                    # is at least better than having no information at all.
                    is_array = var_info1[0].indices is not None
            except KeyError:
                # TODO #845: Once we have symbol tables, any variable should
                # be in a symbol table, so we have to raise an exception here.
                # We need to fall-back to the old-style test, since we do not
                # have information in a symbol table. So check if the access
                # information stored an index:
                is_array = var_info1[0].indices is not None

            if not is_array:
                NemoLoopFuseTrans.validate_written_scalar(var_info1, var_info2)
            else:
                NemoLoopFuseTrans.validate_written_array(
                    var_info1, var_info2, loop_var1)
コード例 #15
0
ファイル: nemo_loop_fuse.py プロジェクト: hiker/PSyclone
    def validate_written_array(var_info1, var_info2, loop_variable):
        '''Validates if the accesses to an array, which is at least written
        once, allows loop fusion. The access pattern to this array is
        specified in the two parameters (which includes the name of the
        variable).

        :param var_info1: access information for variable in the first loop.
        :type var_info1: :py:class:`psyclone.core.var_info.VariableAccessInfo`
        :param var_info2: access information for variable in the second loop.
        :type var_info2: :py:class:`psyclone.core.var_info.VariableAccessInfo`
        :param loop_variable: symbol of the variable associated with the \
            loops being fused.
        :type loop_variable: \
            :py:class:`psyclone.psyir.symbols.datasymbol.DataSymbol`

        :raises TransformationError: an array that is written to uses \
            inconsistent indices, e.g. a(i,j) and a(j,i).

        '''
        # First check if all accesses to the array have the same dimension
        # based on the loop variable
        # Now detect which dimension(s) is/are used in the loop. For example
        # if a "do j..." loop is one of the fused loops, consider expressions
        # like a(i,j) and a(j+2, i-1) in the two loops:
        # In this case the dimensions 1 (a(i,j)) and 0 (a(j+2,i-1)) would
        # be accessed. Since the variable is written somewhere (read-only
        # was tested above), loop fusion will likely result in invalid code.
        # Additionally, collect all indices that are actually used, since
        # they are needed in a test further down.
        all_accesses = var_info1.all_accesses + var_info2.all_accesses

        found_dimension_index = -1
        all_indices = []

        # Loop over all the accesses of this variable
        for access in all_accesses:
            list_of_indices = access.indices
            # Now determine all dimensions that depend
            # on the loop variable:
            for dimension_index, index_expression in \
                    enumerate(list_of_indices):
                accesses = VariablesAccessInfo()

                index_expression.reference_accesses(accesses)
                if Signature(loop_variable.name) not in accesses:
                    continue

                # If a previously identified index location does not match
                # the current index location (e.g. a(i,j), and a(j,i) ), then
                # the loop in general cannot be fused.
                if found_dimension_index > -1 and \
                        found_dimension_index != dimension_index:
                    raise TransformationError(
                        "Variable '{0}' is written to in one or both of the "
                        "loops and the loop variable {1} is used in "
                        "different index locations ({2} and {3}) when "
                        "accessing it.".format(var_info1.var_name,
                                               loop_variable.name,
                                               found_dimension_index,
                                               dimension_index))

                found_dimension_index = dimension_index
                all_indices.append(index_expression)

        if not all_indices:
            # An array is used that is not actually dependent on the
            # loop variable. This means the variable can not always be safely
            # fused.
            # do j=1, n
            #    a(1) = b(j)+1
            # enddo
            # do j=1, n
            #    c(j) = a(1) * 2
            # enddo
            # More tests could be done here, e.g. to see if it can be shown
            # that each access in the first loop is different from the
            # accesses in the second loop: a(1) in first, a(2) in second.
            # Other special cases: reductions (a(1) = a(1) + x),
            # array expressions : a(:) = b(j) * x(:)
            # Potentially this could use the scalar handling code!
            raise TransformationError(
                "Variable '{0}' does not depend on loop variable '{1}', "
                "but is read and written.".format(var_info1.var_name,
                                                  loop_variable.name))
コード例 #16
0
ファイル: nemo_loop_fuse.py プロジェクト: stfc/PSyclone
    def validate(self, node1, node2, options=None):
        ''' Perform NEMO API specific validation checks before applying
        the transformation.

        :param node1: the first Node that is being checked.
        :type node1: :py:class:`psyclone.psyir.nodes.Node`
        :param node2: the second Node that is being checked.
        :type node2: :py:class:`psyclone.psyir.nodes.Node`
        :param options: a dict with options for transformations.
        :type options: dict of string:values or None

        :raises TransformationError: if the lower or upper loop boundaries \
            are not the same.
        :raises TransformationError: if the loop step size is not the same.
        :raises TransformationError: if the loop variables are not the same.

        '''
        # pylint: disable=too-many-locals
        # First check constraints on the nodes inherited from the parent
        # LoopFuseTrans:
        super(NemoLoopFuseTrans, self).validate(node1, node2, options)

        if not node1.start_expr.math_equal(node2.start_expr):
            raise TransformationError("Lower loop bounds must be identical, "
                                      "but are '{0}'' and '{1}'".format(
                                          node1.start_expr, node2.start_expr))
        if not node1.stop_expr.math_equal(node2.stop_expr):
            raise TransformationError("Upper loop bounds must be identical, "
                                      "but are '{0}'' and '{1}'".format(
                                          node1.stop_expr, node2.stop_expr))
        if not node1.step_expr.math_equal(node2.step_expr):
            raise TransformationError("Step size in loops must be identical, "
                                      "but are '{0}'' and '{1}'".format(
                                          node1.step_expr, node2.step_expr))
        loop_var1 = node1.variable
        loop_var2 = node2.variable

        if loop_var1 != loop_var2:
            raise TransformationError("Loop variables must be the same, "
                                      "but are '{0}' and '{1}'".format(
                                          loop_var1.name, loop_var2.name))
        vars1 = VariablesAccessInfo(node1)
        vars2 = VariablesAccessInfo(node2)

        # Get all variables that occur in both loops. A variable
        # that is only in one loop is not affected by fusion.
        all_vars = set(vars1).intersection(vars2)
        symbol_table = node1.scope.symbol_table

        for signature in all_vars:
            var_name = str(signature)
            # Ignore the loop variable
            if var_name == loop_var1.name:
                continue
            var_info1 = vars1[signature]
            var_info2 = vars2[signature]

            # Variables that are only read in both loops can always be fused
            if var_info1.is_read_only() and var_info2.is_read_only():
                continue

            symbol = symbol_table.lookup(var_name)
            # TODO #1270 - the is_array_access function might be moved
            is_array = symbol.is_array_access(access_info=var_info1)

            if not is_array:
                NemoLoopFuseTrans.validate_written_scalar(var_info1, var_info2)
            else:
                NemoLoopFuseTrans.validate_written_array(
                    var_info1, var_info2, loop_var1)