Beispiel #1
0
    def gen_use(self, symbol, symbol_table):
        ''' Performs consistency checks and then creates and returns the
        Fortran use statement(s) for this ContainerSymbol as required for
        the supplied symbol table. If this symbol has both a wildcard import
        and explicit imports then two use statements are generated. (This
        means that when generating Fortran from PSyIR created from Fortran
        code, we replicate the structure of the original.)

        :param symbol: the container symbol instance.
        :type symbol: :py:class:`psyclone.psyir.symbols.ContainerSymbol`
        :param symbol_table: the symbol table containing this container symbol.
        :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable`

        :returns: the Fortran use statement(s) as a string.
        :rtype: str

        :raises VisitorError: if the symbol argument is not a ContainerSymbol.
        :raises VisitorError: if the symbol_table argument is not a \
                            SymbolTable.
        :raises VisitorError: if the supplied symbol is not in the supplied \
                            SymbolTable.
        :raises VisitorError: if the supplied symbol has the same name as an \
                            entry in the SymbolTable but is a different object.
        '''
        if not isinstance(symbol, ContainerSymbol):
            raise VisitorError(
                "gen_use() expects a ContainerSymbol as its first argument "
                "but got '{0}'".format(type(symbol).__name__))
        if not isinstance(symbol_table, SymbolTable):
            raise VisitorError(
                "gen_use() expects a SymbolTable as its second argument but "
                "got '{0}'".format(type(symbol_table).__name__))
        if symbol.name not in symbol_table:
            raise VisitorError("gen_use() - the supplied symbol ('{0}') is not"
                               " in the supplied SymbolTable.".format(
                                   symbol.name))
        if symbol_table.lookup(symbol.name) is not symbol:
            raise VisitorError(
                "gen_use() - the supplied symbol ('{0}') is not the same "
                "object as the entry with that name in the supplied "
                "SymbolTable.".format(symbol.name))

        # Construct the list of symbol names for the ONLY clause
        only_list = [
            dsym.name for dsym in symbol_table.imported_symbols(symbol)
        ]

        # Finally construct the use statements for this Container (module)
        if not only_list and not symbol.wildcard_import:
            # We have a "use xxx, only:" - i.e. an empty only list
            return "{0}use {1}, only :\n".format(self._nindent, symbol.name)
        use_stmts = ""
        if only_list:
            use_stmts = "{0}use {1}, only : {2}\n".format(
                self._nindent, symbol.name, ", ".join(sorted(only_list)))
        # It's possible to have both explicit and wildcard imports from the
        # same Fortran module.
        if symbol.wildcard_import:
            use_stmts += "{0}use {1}\n".format(self._nindent, symbol.name)
        return use_stmts
Beispiel #2
0
    def gen_vardecl(self, symbol):
        '''Create and return the Fortran variable declaration for this Symbol.

        :param symbol: the symbol instance.
        :type symbol: :py:class:`psyclone.psyir.symbols.DataSymbol`

        :returns: the Fortran variable declaration as a string.
        :rtype: str

        :raises VisitorError: if the symbol does not specify a \
            variable declaration (it is not a local declaration or an \
            argument declaration).
        :raises VisitorError: if the symbol has an array with a shape \
            containing a mixture of DEFERRED and other extents.
        '''
        if not (symbol.is_local or symbol.is_argument):
            raise VisitorError(
                "gen_vardecl requires the symbol '{0}' to have a Local or "
                "an Argument interface but found a '{1}' interface."
                "".format(symbol.name,
                          type(symbol.interface).__name__))

        datatype = gen_datatype(symbol)
        result = "{0}{1}".format(self._nindent, datatype)
        if ArrayType.Extent.DEFERRED in symbol.shape:
            if not all(dim == ArrayType.Extent.DEFERRED
                       for dim in symbol.shape):
                raise VisitorError(
                    "A Fortran declaration of an allocatable array must have"
                    " the extent of every dimension as 'DEFERRED' but "
                    "symbol '{0}' has shape: {1}".format(
                        symbol.name, symbol.shape))
            # A 'deferred' array extent means this is an allocatable array
            result += ", allocatable"
        if ArrayType.Extent.ATTRIBUTE in symbol.shape:
            if not all(dim == ArrayType.Extent.ATTRIBUTE
                       for dim in symbol.shape):
                # If we have an 'assumed-size' array then only the last
                # dimension is permitted to have an 'ATTRIBUTE' extent
                if symbol.shape.count(ArrayType.Extent.ATTRIBUTE) != 1 or \
                   symbol.shape[-1] != ArrayType.Extent.ATTRIBUTE:
                    raise VisitorError(
                        "An assumed-size Fortran array must only have its "
                        "last dimension unspecified (as 'ATTRIBUTE') but "
                        "symbol '{0}' has shape: {1}".format(
                            symbol.name, symbol.shape))
        dims = gen_dims(symbol)
        if dims:
            result += ", dimension({0})".format(",".join(dims))
        intent = gen_intent(symbol)
        if intent:
            result += ", intent({0})".format(intent)
        if symbol.is_constant:
            result += ", parameter"
        result += " :: {0}".format(symbol.name)
        if symbol.is_constant:
            result += " = {0}".format(self._visit(symbol.constant_value))
        result += "\n"
        return result
Beispiel #3
0
    def nemoloop_node(self, loop_node):
        '''Supported NEMO loops are triply nested with particular indices (not
        yet checked) and should contain a NemoKern. If this is not the
        case then it is not possible to translate so an exception is
        raised.

        :param loop_node: a NemoLoop PSyIR node.
        :type loop_node: subclass of :py:class:`psyclone.nemo.NemoLoop`

        :returns: the SIR Python code.
        :rtype: str

        :raises VisitorError: if the loop is not triply nested with \
        computation within the triply nested loop.

        '''
        # Check first loop has a single loop as a child.
        loop_content = loop_node.loop_body.children
        if not (len(loop_content) == 1
                and isinstance(loop_content[0], NemoLoop)):
            raise VisitorError("Child of loop should be a single loop.")

        # Check second loop has a single loop as a child.
        loop_content = loop_content[0].loop_body.children
        if not (len(loop_content) == 1
                and isinstance(loop_content[0], NemoLoop)):
            raise VisitorError(
                "Child of child of loop should be a single loop.")

        # Check third loop has a single NemoKern as a child.
        loop_content = loop_content[0].loop_body.children
        if not (len(loop_content) == 1
                and isinstance(loop_content[0], NemoKern)):
            raise VisitorError(
                "Child of child of child of loop should be a NemoKern.")

        # The interval values are hardcoded for the moment (see #470).
        result = ("{0}interval = make_interval(Interval.Start, Interval.End, "
                  "0, 0)\n".format(self._nindent))
        result += ("{0}body_ast = make_ast([\n".format(self._nindent))
        self._depth += 1
        result += self.nemokern_node(loop_content[0])
        self._depth -= 1
        # Remove the trailing comma if there is one as this is the
        # last entry in make_ast.
        result = result.rstrip(",\n") + "\n"
        result += "{0}])\n".format(self._nindent)
        # For the moment there is a hard coded assumption that the
        # vertical looping is in the forward (1..n) direction (see
        # #470).
        result += (
            "{0}vertical_region_fns.append(make_vertical_region_decl_stmt("
            "body_ast, interval, VerticalRegion.Forward))\n"
            "".format(self._nindent))
        return result
Beispiel #4
0
    def container_node(self, node):
        '''This method is called when a Container instance is found in
        the PSyIR tree.

        A container node is mapped to a module in the Fortran back end.

        :param node: a Container PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.Container`

        :returns: the Fortran code as a string.
        :rtype: str

        :raises VisitorError: if the name attribute of the supplied \
        node is empty or None.
        :raises VisitorError: if any of the children of the supplied \
        Container node are not KernelSchedules.

        '''
        if not node.name:
            raise VisitorError("Expected Container node name to have a value.")

        # All children must be KernelSchedules as modules within
        # modules are not supported.
        if not all(
            [isinstance(child, KernelSchedule) for child in node.children]):
            raise VisitorError(
                "The Fortran back-end requires all children of a Container "
                "to be KernelSchedules.")

        result = "{0}module {1}\n".format(self._nindent, node.name)

        self._depth += 1

        # Declare the Container's data and specify that Containers do
        # not allow argument declarations.
        declarations = self.gen_decls(node.symbol_table, args_allowed=False)

        # Get the subroutine statements.
        subroutines = ""
        for child in node.children:
            subroutines += self._visit(child)

        result += ("{1}\n"
                   "{0}contains\n"
                   "{2}\n"
                   "".format(self._nindent, declarations, subroutines))

        self._depth -= 1
        result += "{0}end module {1}\n".format(self._nindent, node.name)
        return result
Beispiel #5
0
    def gen_id_variable(self, symbol, dimension_index):
        '''
        Generate the declaration and initialisation of a variable identifying
        an OpenCL work-item. IDs are initialised by the OpenCL function:
        `size_t get_global_id(uint dimindx)`

        :param symbol: The symbol instance.
        :type symbol: :py:class:`psyclone.psyir.symbols.DataSymbol`
        :param int dimension_index: Dimension which the given symbol will \
            iterate on.

        :returns: OpenCL declaration of an OpenCL id variable.
        :rtype: str

        :raises VisitorError: if symbol is not a scalar integer
        '''
        if (not isinstance(symbol.datatype, ScalarType)
                or symbol.datatype.intrinsic != ScalarType.Intrinsic.INTEGER):
            raise VisitorError(
                "OpenCL work-item identifiers must be scalar integer symbols "
                "but found {0}.".format(str(symbol)))

        code = ""
        code += self._nindent + "int " + symbol.name
        code += " = get_global_id(" + str(dimension_index) + ");\n"
        return code
Beispiel #6
0
    def codeblock_node(self, node):
        '''This method is called when a CodeBlock instance is found in the
        PSyIR tree. It returns the content of the CodeBlock as a
        Fortran string, indenting as appropriate.

        At the moment it is not possible to distinguish between a
        codeblock that is one or more full lines (and therefore needs
        a newline added) and a codeblock that is part of a line (and
        therefore does not need a newline). The current implementation
        adds a newline irrespective. This is the subject of issue
        #388.

        :param node: a CodeBlock PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.CodeBlock`

        :returns: the Fortran code as a string.
        :rtype: str

        '''
        from psyclone.psyir.nodes import CodeBlock
        result = ""
        if node.structure == CodeBlock.Structure.STATEMENT:
            # indent and newlines required
            for ast_node in node.get_ast_nodes:
                result += "{0}{1}\n".format(self._nindent, str(ast_node))
        elif node.structure == CodeBlock.Structure.EXPRESSION:
            for ast_node in node.get_ast_nodes:
                result += str(ast_node)
        else:
            raise VisitorError(("Unsupported CodeBlock Structure '{0}' found."
                                "".format(node.structure)))
        return result
Beispiel #7
0
    def node_node(self, node):
        '''Catch any unsupported nodes, output their class names and continue
        down the node hierarchy (if skip_node is set to True). This is
        useful for debugging and differs from the base class
        implementation of skip_nodes which silently continues. If
        skip_nodes is set to False then raise an exception if an
        unsupported node is found.

        :param node: an unsupported PSyIR node.
        :type node: subclass of :py:class:`psyclone.psyir.nodes.Node`

        :returns: the SIR Python code.
        :rtype: str

        :raises VisitorError: if skip_nodes is set to False.

        '''
        if not self._skip_nodes:
            raise VisitorError(
                "Class SIRWriter method node_node(), unsupported node "
                "found '{0}'".format(type(node)))
        result = "{0}[ {1} start ]\n".format(self._nindent,
                                             type(node).__name__)
        self._depth += 1
        for child in node.children:
            result += self._visit(child)
        self._depth -= 1
        result += "{0}[ {1} end ]\n".format(self._nindent, type(node).__name__)
        return result
Beispiel #8
0
def gen_intent(symbol):
    '''Given a DataSymbol instance as input, determine the Fortran intent that
    the DataSymbol should have and return the value as a string.

    :param symbol: the symbol instance.
    :type symbol: :py:class:`psyclone.psyir.symbols.DataSymbol`

    :returns: the Fortran intent of the symbol instance in lower case, \
    or None if the access is unknown or if this is a local variable.
    :rtype: str or NoneType

    '''
    mapping = {
        ArgumentInterface.Access.UNKNOWN: None,
        ArgumentInterface.Access.READ: "in",
        ArgumentInterface.Access.WRITE: "out",
        ArgumentInterface.Access.READWRITE: "inout"
    }

    if symbol.is_argument:
        try:
            return mapping[symbol.interface.access]
        except KeyError as excinfo:
            raise VisitorError("Unsupported access '{0}' found."
                               "".format(str(excinfo)))
    else:
        return None  # non-Arguments do not have intent
Beispiel #9
0
    def reference_node(self, node):
        '''This method is called when a Reference instance is found in the
        PSyIR tree.

        :param node: a Reference PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.Reference`

        :returns: the SIR Python code.
        :rtype: str

        :raises VisitorError: if this node has children.

        '''
        if node.children:
            raise VisitorError(
                "Method reference_node in class SIRWriter: SIR Reference "
                "node is not expected to have any children.")
        # _scalar_names is a set so duplicates will be ignored. It
        # captures all unique scalar names as scalars are currently
        # treated as temporaries (#521 captures this). The simplest
        # way to declare a scalar temporary in Dawn is to treat it as
        # a field temporary (as the Dawn backend works out if a scalar
        # is required).
        self._scalar_names.add(node.name)

        return "{0}make_field_access_expr(\"{1}\")".format(
            self._nindent, node.name)
Beispiel #10
0
def gen_stencil(node):
    '''Given an array access as input, determine the form of stencil
    access and return it in the form expected by the SIR as a
    string. Raise an exception if the array access is not a recognised
    stencil access.

    :param node: an array access.
    :type node: :py:class:`psyclone.psyir.nodes.Array`

    :returns: the SIR stencil access format for the array access.
    :rtype: str

    :raises VisitorError: if the node is not the expected type or the \
    array access is not in a recognised stencil form.

    '''
    if not isinstance(node, Array):
        raise VisitorError(
            "gen_stencil expected an Array as input but found '{0}'."
            "".format(type(node)))
    dims = []
    for child in node.children:
        if isinstance(child, Reference):
            dims.append("0")
        elif isinstance(child, BinaryOperation):
            if isinstance(child.children[0], Reference) and \
               isinstance(child.children[1], Literal):
                if child.operator == BinaryOperation.Operator.SUB:
                    dims.append("-" + child.children[1].value)
                elif child.operator == BinaryOperation.Operator.ADD:
                    dims.append(child.children[1].value)
                else:
                    raise VisitorError(
                        "gen_stencil unsupported stencil operator found "
                        "'{0}'. Expecting '+' or '-'."
                        "".format(child.operator.name))
            else:
                raise VisitorError(
                    "gen_stencil unsupported stencil index found '{0}'."
                    "".format(str(child)))
        else:
            raise VisitorError(
                "gen_stencil unsupported (non-stencil) index found '{0}'."
                "".format(str(child)))
    return "[{0}]".format(", ".join(dims))
Beispiel #11
0
    def codeblock_node(self, _):
        # pylint: disable=no-self-use
        '''This method is called when a CodeBlock instance is found in the
        PSyIR tree. At the moment all CodeBlocks contain Fortran fparser
        code.

        :raises VisitorError: The CodeBlock can not be translated to C.

        '''
        raise VisitorError("CodeBlocks can not be translated to C.")
Beispiel #12
0
    def unaryoperation_node(self, node):
        '''This method is called when a UnaryOperation instance is found in
        the PSyIR tree.

        :param node: a UnaryOperation PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.UnaryOperation`

        :returns: the SIR Python code.
        :rtype: str

        :raises VisitorError: if there is no mapping from the PSyIR \
        operator to SIR, or if the child of the PSyIR operator is not \
        a literal (as only -<literal> is currently supported).

        '''
        # Currently only '-' is supported in the SIR mapping.
        unary_operators = {UnaryOperation.Operator.MINUS: '-'}
        try:
            oper = unary_operators[node.operator]
        except KeyError:
            raise VisitorError(
                "Method unaryoperation_node in class SIRWriter, unsupported "
                "operator '{0}' found.".format(str(node.operator)))
        # Currently only '-<literal>' is supported in the SIR mapping.
        if not (len(node.children) == 1
                and isinstance(node.children[0], Literal)):
            raise VisitorError(
                "Currently, unary operators can only be applied to literals.")
        literal = node.children[0]
        if literal.datatype.intrinsic not in [
                ScalarType.Intrinsic.REAL, ScalarType.Intrinsic.INTEGER
        ]:
            # The '-' operator can only be applied to REAL and INTEGER
            # datatypes.
            raise VisitorError(
                "PSyIR type '{0}' does not work with the '-' operator."
                "".format(str(literal.datatype)))
        result = literal.value
        datatype = TYPE_MAP_TO_SIR[literal.datatype.intrinsic]
        return ("{0}make_literal_access_expr(\"{1}{2}\", {3})"
                "".format(self._nindent, oper, result, datatype))
Beispiel #13
0
    def binaryoperation_node(self, node):
        '''This method is called when a BinaryOperation instance is found in
        the PSyIR tree.

        :param node: a BinaryOperation PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.BinaryOperation`

        :returns: the SIR Python code.
        :rtype: str

        :raises VisitorError: if there is no mapping from the PSyIR \
        operator to SIR.

        '''
        binary_operators = {
            BinaryOperation.Operator.ADD: '+',
            BinaryOperation.Operator.SUB: '-',
            BinaryOperation.Operator.MUL: '*',
            BinaryOperation.Operator.DIV: '/',
            BinaryOperation.Operator.POW: '**',
            BinaryOperation.Operator.EQ: '==',
            BinaryOperation.Operator.NE: '!=',
            BinaryOperation.Operator.LE: '<=',
            BinaryOperation.Operator.LT: '<',
            BinaryOperation.Operator.GE: '>=',
            BinaryOperation.Operator.GT: '>',
            BinaryOperation.Operator.AND: '&&',
            BinaryOperation.Operator.OR: '||'
        }

        self._depth += 1
        lhs = self._visit(node.children[0])
        try:
            oper = binary_operators[node.operator]
        except KeyError:
            raise VisitorError(
                "Method binaryoperation_node in class SIRWriter, unsupported "
                "operator '{0}' found.".format(str(node.operator)))
        rhs = self._visit(node.children[1])
        self._depth -= 1
        result = "{0}make_binary_operator(\n{1}".format(self._nindent, lhs)
        # For better formatting, remove the newline if one exists.
        result = result.rstrip("\n")
        result += ",\n"
        result += ("{0}{1}\"{2}\",\n{3}\n{0}{1})\n"
                   "".format(self._nindent, self._indent, oper, rhs))
        return result
Beispiel #14
0
    def ifblock_node(self, node):
        '''This method is called when an IfBlock instance is found in the
        PSyIR tree.

        :param node: An IfBlock PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.IfBlock`

        :returns: The C code as a string.
        :rtype: str

        :raises VisitorError: If node has fewer children than expected.

        '''
        if len(node.children) < 2:
            raise VisitorError(
                "IfBlock malformed or incomplete. It should have at least "
                "2 children, but found {0}.".format(len(node.children)))

        condition = self._visit(node.condition)

        self._depth += 1
        if_body = ""
        for child in node.if_body:
            if_body += self._visit(child)
        else_body = ""
        # node.else_body is None if there is no else clause.
        if node.else_body:
            for child in node.else_body:
                else_body += self._visit(child)
        self._depth -= 1

        if else_body:
            result = ("{0}if ({1}) {{\n"
                      "{2}"
                      "{0}}} else {{\n"
                      "{3}"
                      "{0}}}\n"
                      "".format(self._nindent, condition, if_body, else_body))
        else:
            result = ("{0}if ({1}) {{\n"
                      "{2}"
                      "{0}}}\n"
                      "".format(self._nindent, condition, if_body))
        return result
Beispiel #15
0
    def binaryoperation_node(self, node):
        '''This method is called when a BinaryOperation instance is found in
        the PSyIR tree.

        :param node: a BinaryOperation PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.BinaryOperation`

        :returns: the Fortran code as a string.
        :rtype: str

        '''
        lhs = self._visit(node.children[0])
        rhs = self._visit(node.children[1])
        try:
            fort_oper = get_fortran_operator(node.operator)
            if is_fortran_intrinsic(fort_oper):
                # This is a binary intrinsic function.
                return "{0}({1}, {2})".format(fort_oper, lhs, rhs)
            parent = node.parent
            if isinstance(parent, Operation):
                # We may need to enforce precedence
                parent_fort_oper = get_fortran_operator(parent.operator)
                if not is_fortran_intrinsic(parent_fort_oper):
                    # We still may need to enforce precedence
                    if precedence(fort_oper) < precedence(parent_fort_oper):
                        # We need brackets to enforce precedence
                        return "({0} {1} {2})".format(lhs, fort_oper, rhs)
                    if precedence(fort_oper) == precedence(parent_fort_oper):
                        # We still may need to enforce precedence
                        if (isinstance(parent, UnaryOperation)
                                or (isinstance(parent, BinaryOperation)
                                    and parent.children[1] == node)):
                            # We need brackets to enforce precedence
                            # as a) a unary operator is performed
                            # before a binary operator and b) floating
                            # point operations are not actually
                            # associative due to rounding errors.
                            return "({0} {1} {2})".format(lhs, fort_oper, rhs)
            return "{0} {1} {2}".format(lhs, fort_oper, rhs)
        except KeyError:
            raise VisitorError("Unexpected binary op '{0}'."
                               "".format(node.operator))
Beispiel #16
0
    def literal_node(self, node):
        '''This method is called when a Literal instance is found in the PSyIR
        tree.

        :param node: a Literal PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.Literal`

        :returns: the SIR Python code.
        :rtype: str

        '''
        result = node.value
        try:
            datatype = TYPE_MAP_TO_SIR[node.datatype.intrinsic]
        except KeyError:
            raise VisitorError(
                "PSyIR type '{0}' has no representation in the SIR backend."
                "".format(str(node.datatype)))

        return ("{0}make_literal_access_expr(\"{1}\", {2})"
                "".format(self._nindent, result, datatype))
Beispiel #17
0
    def kernelschedule_node(self, node):
        '''This method is called when a KernelSchedule instance is found in
        the PSyIR tree.

        The constants_mod module is currently hardcoded into the
        output as it is required for LFRic code. When issue #375 has
        been addressed this module can be added only when required.

        :param node: a KernelSchedule PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.KernelSchedule`

        :returns: the Fortran code as a string.
        :rtype: str

        :raises VisitorError: if the name attribute of the supplied \
        node is empty or None.

        '''
        if not node.name:
            raise VisitorError("Expected node name to have a value.")

        args = [symbol.name for symbol in node.symbol_table.argument_list]
        result = ("{0}subroutine {1}({2})\n"
                  "".format(self._nindent, node.name, ",".join(args)))

        self._depth += 1
        # Declare the kernel data.
        declarations = self.gen_decls(node.symbol_table)
        # Get the executable statements.
        exec_statements = ""
        for child in node.children:
            exec_statements += self._visit(child)
        result += ("{0}\n" "{1}\n" "".format(declarations, exec_statements))

        self._depth -= 1
        result += ("{0}end subroutine {1}\n"
                   "".format(self._nindent, node.name))

        return result
Beispiel #18
0
    def naryoperation_node(self, node):
        '''This method is called when an NaryOperation instance is found in
        the PSyIR tree.

        :param node: an NaryOperation PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.NaryOperation`

        :returns: the Fortran code as a string.
        :rtype: str

        :raises VisitorError: if an unexpected N-ary operator is found.

        '''
        arg_list = []
        for child in node.children:
            arg_list.append(self._visit(child))
        try:
            fort_oper = get_fortran_operator(node.operator)
            return "{0}({1})".format(fort_oper, ", ".join(arg_list))
        except KeyError:
            raise VisitorError("Unexpected N-ary op '{0}'".format(
                node.operator))
Beispiel #19
0
    def gen_array_length_variables(self, symbol, symtab=None):
        '''
        Generate length variable declaration and initialisation for each array
        dimension of the given symbol. By convention they are named:
        <name>LEN<DIM>, and initialised using the function:
        `size_t get_global_size(uint dimindx)`.


        :param symbol: The symbol instance.
        :type symbol: :py:class:`psyclone.psyir.symbols.DataSymbol`
        :param symtab: The symbol table from the given symbol context to \
            check for name clashes.
        :type symtab: :py:class:`psyclone.psyir.symbols.SymbolTable`

        :return: OpenCL declaration and initialisation of length variables.
        :rtype: str

        :raises VisitorError: if the array length variable name clashes \
            with another symbol name.
        '''

        code = ""
        dimensions = len(symbol.shape)
        for dim in range(1, dimensions + 1):
            code += self._nindent + "int "
            varname = symbol.name + "LEN" + str(dim)

            # Check there is no clash with other variables
            if symtab and varname in symtab:
                raise VisitorError(
                    "Unable to declare the variable '{0}' to store the "
                    "length of '{1}' because the Symbol Table already "
                    "contains a symbol with the same name."
                    "".format(varname, symbol.name))

            code += varname + " = get_global_size("
            code += str(dim - 1) + ");\n"
        return code
Beispiel #20
0
    def unaryoperation_node(self, node):
        '''This method is called when a UnaryOperation instance is found in
        the PSyIR tree.

        :param node: a UnaryOperation PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.UnaryOperation`

        :returns: the Fortran code as a string.
        :rtype: str

        :raises VisitorError: if an unexpected Unary op is encountered.

        '''
        content = self._visit(node.children[0])
        try:
            fort_oper = get_fortran_operator(node.operator)
            if is_fortran_intrinsic(fort_oper):
                # This is a unary intrinsic function.
                return "{0}({1})".format(fort_oper, content)
            return "{0}{1}".format(fort_oper, content)
        except KeyError:
            raise VisitorError("Unexpected unary op '{0}'.".format(
                node.operator))
Beispiel #21
0
    def array_node(self, node):
        '''This method is called when an Array instance is found in the PSyIR
        tree.

        :param node: An Array PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.Array`

        :returns: The C code as a string.
        :rtype: str

        :raises VisitorError: If this node has no children.

        '''
        code = node.name + "["

        dimensions_remaining = len(node.children)
        if dimensions_remaining < 1:
            raise VisitorError(
                "Arrays must have at least 1 dimension but found node: '{0}'."
                "".format(str(node)))

        # In C array expressions should be reversed from the PSyIR order
        # (column-major to row-major order) and flattened (1D).
        for child in reversed(node.children):
            code = code + self._visit(child)
            # For each dimension bigger than one, it needs to write the
            # appropriate operation to flatten the array. By convention,
            # the array dimensions are <name>LEN<DIM>.
            # (e.g. A[3,5,2] -> A[3 * ALEN2 * ALEN1 + 5 * ALEN1 + 2])
            for dim in reversed(range(1, dimensions_remaining)):
                dimstring = node.name + "LEN" + str(dim)
                code = code + " * " + dimstring
            dimensions_remaining = dimensions_remaining - 1
            code = code + " + "

        code = code[:-3] + "]"  # Delete last ' + ' and close bracket
        return code
Beispiel #22
0
    def gen_decls(self, symbol_table, args_allowed=True):
        '''Create and return the Fortran declarations for the supplied
        SymbolTable.

        :param symbol_table: the SymbolTable instance.
        :type symbol: :py:class:`psyclone.psyir.symbols.SymbolTable`
        :param bool args_allowed: if False then one or more argument \
                declarations in symbol_table will cause this method to raise \
                an exception. Defaults to True.

        :returns: the Fortran declarations as a string.
        :rtype: str

        :raises VisitorError: if one of the symbols is a RoutineSymbol \
            which does not have a GlobalInterface or LocalInterface as this \
            is not supported by this backend.
        :raises VisitorError: if args_allowed is False and one or more \
                              argument declarations exist in symbol_table.
        :raises VisitorError: if any symbols representing variables (i.e. \
            not kind parameters) without an explicit declaration or 'use' \
            are encountered.

        '''
        declarations = ""

        if not all([
                isinstance(symbol.interface, (LocalInterface, GlobalInterface))
                for symbol in symbol_table.symbols
                if isinstance(symbol, RoutineSymbol)
        ]):
            raise VisitorError(
                "Routine symbols without a global or local interface are not "
                "supported by the Fortran back-end.")

        # Does the symbol table contain any symbols with a deferred
        # interface (i.e. we don't know how they are brought into scope) that
        # are not KIND parameters?
        unresolved_datasymbols = symbol_table.get_unresolved_datasymbols(
            ignore_precision=True)
        if unresolved_datasymbols:
            symbols_txt = ", ".join(
                ["'" + sym + "'" for sym in unresolved_datasymbols])
            raise VisitorError(
                "The following symbols are not explicitly declared or imported"
                " from a module (in the local scope) and are not KIND "
                "parameters: {0}".format(symbols_txt))

        # Fortran requires use statements to be specified before
        # variable declarations. As a convention, this method also
        # declares any argument variables before local variables.

        # 1: Use statements
        for symbol in symbol_table.containersymbols:
            declarations += self.gen_use(symbol, symbol_table)

        # 2: Argument variable declarations
        if symbol_table.argument_datasymbols and not args_allowed:
            raise VisitorError(
                "Arguments are not allowed in this context but this symbol "
                "table contains argument(s): '{0}'."
                "".format([
                    symbol.name for symbol in symbol_table.argument_datasymbols
                ]))
        for symbol in symbol_table.argument_datasymbols:
            declarations += self.gen_vardecl(symbol)

        # 3: Local variable declarations
        for symbol in symbol_table.local_datasymbols:
            declarations += self.gen_vardecl(symbol)

        # 4: Accessibility statements for routine symbols
        declarations += self.gen_routine_access_stmts(symbol_table)

        return declarations
Beispiel #23
0
    def binaryoperation_node(self, node):
        '''This method is called when a BinaryOperation instance is found in
        the PSyIR tree.

        :param node: A BinaryOperation PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.BinaryOperation`

        :returns: The C code as a string.
        :rtype: str

        :raises VisitorError: If this node has fewer children than expected.
        :raises NotImplementedError: If the operator is not supported by the \
            C backend.

        '''
        if len(node.children) != 2:
            raise VisitorError("BinaryOperation malformed or incomplete. It "
                               "should have exactly 2 children, but found {0}."
                               "".format(len(node.children)))

        def operator_format(operator_str, expr1, expr2):
            '''
            :param str operator_str: String representing the operator.
            :param str expr1: String representation of the LHS operand.
            :param str expr2: String representation of the RHS operand.

            :returns: C language operator expression.
            :rtype: str
            '''
            return "(" + expr1 + " " + operator_str + " " + expr2 + ")"

        def function_format(function_str, expr1, expr2):
            '''
            :param str function_str: Name of the function.
            :param str expr1: String representation of the first operand.
            :param str expr2: String representation of the second operand.

            :returns: C language binary function expression.
            :rtype: str
            '''
            return function_str + "(" + expr1 + ", " + expr2 + ")"

        # Define a map with the operator string and the formatter function
        # associated with each BinaryOperation.Operator
        from psyclone.psyir.nodes import BinaryOperation
        opmap = {
            BinaryOperation.Operator.ADD: ("+", operator_format),
            BinaryOperation.Operator.SUB: ("-", operator_format),
            BinaryOperation.Operator.MUL: ("*", operator_format),
            BinaryOperation.Operator.DIV: ("/", operator_format),
            BinaryOperation.Operator.REM: ("%", operator_format),
            BinaryOperation.Operator.POW: ("pow", function_format),
            BinaryOperation.Operator.EQ: ("==", operator_format),
            BinaryOperation.Operator.NE: ("!=", operator_format),
            BinaryOperation.Operator.LT: ("<", operator_format),
            BinaryOperation.Operator.LE: ("<=", operator_format),
            BinaryOperation.Operator.GT: (">", operator_format),
            BinaryOperation.Operator.GE: (">=", operator_format),
            BinaryOperation.Operator.AND: ("&&", operator_format),
            BinaryOperation.Operator.OR: ("||", operator_format),
            BinaryOperation.Operator.SIGN: ("copysign", function_format),
        }

        # If the instance operator exists in the map, use its associated
        # operator and formatter to generate the code, otherwise raise
        # an Error.
        try:
            opstring, formatter = opmap[node.operator]
        except KeyError:
            raise VisitorError(
                "The C backend does not support the '{0}' operator."
                "".format(node.operator))

        return formatter(opstring, self._visit(node.children[0]),
                         self._visit(node.children[1]))
Beispiel #24
0
    def unaryoperation_node(self, node):
        '''This method is called when a UnaryOperation instance is found in
        the PSyIR tree.

        :param node: A UnaryOperation PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.UnaryOperation`

        :returns: The C code as a string.
        :rtype: str

        :raises VisitorError: If this node has more than one child.
        :raises NotImplementedError: If the operator is not supported by the \
            C backend.

        '''
        if len(node.children) != 1:
            raise VisitorError("UnaryOperation malformed or incomplete. It "
                               "should have exactly 1 child, but found {0}."
                               "".format(len(node.children)))

        def operator_format(operator_str, expr_str):
            '''
            :param str operator_str: String representing the operator.
            :param str expr_str: String representation of the operand.

            :returns: C language operator expression.
            :rtype: str
            '''
            return "(" + operator_str + expr_str + ")"

        def function_format(function_str, expr_str):
            '''
            :param str function_str: Name of the function.
            :param str expr_str: String representation of the operand.

            :returns: C language unary function expression.
            :rtype: str
            '''
            return function_str + "(" + expr_str + ")"

        def cast_format(type_str, expr_str):
            '''
            :param str type_str: Name of the new type.
            :param str expr_str: String representation of the operand.

            :returns: C language unary casting expression.
            :rtype: str
            '''
            return "(" + type_str + ")" + expr_str

        # Define a map with the operator string and the formatter function
        # associated with each UnaryOperation.Operator
        from psyclone.psyir.nodes import UnaryOperation
        opmap = {
            UnaryOperation.Operator.MINUS: ("-", operator_format),
            UnaryOperation.Operator.PLUS: ("+", operator_format),
            UnaryOperation.Operator.NOT: ("!", operator_format),
            UnaryOperation.Operator.SIN: ("sin", function_format),
            UnaryOperation.Operator.COS: ("cos", function_format),
            UnaryOperation.Operator.TAN: ("tan", function_format),
            UnaryOperation.Operator.ASIN: ("asin", function_format),
            UnaryOperation.Operator.ACOS: ("acos", function_format),
            UnaryOperation.Operator.ATAN: ("atan", function_format),
            UnaryOperation.Operator.ABS: ("abs", function_format),
            UnaryOperation.Operator.REAL: ("float", cast_format),
            UnaryOperation.Operator.SQRT: ("sqrt", function_format),
        }

        # If the instance operator exists in the map, use its associated
        # operator and formatter to generate the code, otherwise raise
        # an Error.
        try:
            opstring, formatter = opmap[node.operator]
        except KeyError:
            raise NotImplementedError(
                "The C backend does not support the '{0}' operator."
                "".format(node.operator))

        return formatter(opstring, self._visit(node.children[0]))
Beispiel #25
0
    def kernelschedule_node(self, node):
        '''This method is called when a KernelSchedule instance is found in
        the PSyIR tree.

        :param node: A KernelSchedule PSyIR node.
        :type node: :py:class:`psyclone.psyir.nodes.KernelSchedule`

        :returns: The OpenCL code as a string.
        :rtype: str

        :raises VisitorError: if a non-precision symbol is found with a \
                              deferred interface.
        '''
        # OpenCL implementation assumptions:
        # - All array have the same size and it is given by the
        #   global_work_size argument to clEnqueueNDRangeKernel.
        # - Assumes no dependencies among kernels called concurrently.
        # - All real variables are 64-bit

        # TODO: At the moment, the method caller is responsible to ensure
        # these assumptions. KernelSchedule access to the kernel
        # meta-arguments could be used to check them and also improve the
        # generated code. (Issue #288)

        symtab = node.symbol_table
        data_args = symtab.data_arguments

        # Check that we know where everything in the symbol table
        # comes from.  TODO #592 ultimately precision symbols should
        # be included in this check too as we will need to be able to
        # map from them to the equivalent OpenCL type.
        unresolved_datasymbols = symtab.get_unresolved_datasymbols(
            ignore_precision=True)
        if unresolved_datasymbols:
            symbols_txt = ", ".join(
                ["'" + sym + "'" for sym in unresolved_datasymbols])
            raise VisitorError(
                "Cannot generate OpenCL because the symbol table contains "
                "unresolved data entries (i.e. that have no defined Interface)"
                " which are not used purely to define the precision of other "
                "symbols: {0}".format(symbols_txt))

        # Start OpenCL kernel definition
        code = self._nindent
        if self._kernels_local_size != 1:
            code += "__attribute__((reqd_work_group_size({0}, 1, 1)))\n" \
                    "".format(self._kernels_local_size)
        code += "__kernel void " + node.name + "(\n"
        self._depth += 1
        arguments = []

        # Declare kernel arguments
        for symbol in data_args:
            arguments.append(self._nindent + self.gen_declaration(symbol))
        code += ",\n".join(arguments) + "\n"
        code += self._nindent + "){\n"

        # Declare local variables.
        for symbol in symtab.local_datasymbols:
            code += self.gen_local_variable(symbol)

        # Declare array length
        for symbol in data_args:
            code += self.gen_array_length_variables(symbol, symtab)

        # Declare iteration indices
        for index, symbol in enumerate(symtab.iteration_indices):
            code += self.gen_id_variable(symbol, index)

        # Generate kernel body
        for child in node.children:
            code += self._visit(child)

        # Close kernel definition
        self._depth -= 1
        code += self._nindent + "}\n\n"

        return code
Beispiel #26
0
def gen_datatype(symbol):
    '''Given a DataSymbol instance as input, return the datatype of the
    symbol including any specific precision properties.

    :param symbol: the symbol instance.
    :type symbol: :py:class:`psyclone.psyir.symbols.DataSymbol`

    :returns: the Fortran representation of the symbol's datatype \
              including any precision properties.
    :rtype: str

    :raises NotImplementedError: if the symbol has an unsupported \
    datatype.
    :raises VisitorError: if the symbol specifies explicit precision \
    and this is not supported for the datatype.
    :raises VisitorError: if the size of the explicit precision is not \
    supported for the datatype.
    :raises VisitorError: if the size of the symbol is specified by \
    another variable and the datatype is not one that supports the \
    Fortran KIND option.
    :raises NotImplementedError: if the type of the precision object \
    is an unsupported type.

    '''
    try:
        fortrantype = TYPE_MAP_TO_FORTRAN[symbol.datatype.intrinsic]
    except KeyError:
        raise NotImplementedError(
            "Unsupported datatype '{0}' for symbol '{1}' found in "
            "gen_datatype().".format(symbol.datatype.intrinsic, symbol.name))

    precision = symbol.datatype.precision

    if isinstance(precision, int):
        if fortrantype not in ['real', 'integer', 'logical']:
            raise VisitorError("Explicit precision not supported for datatype "
                               "'{0}' in symbol '{1}' in Fortran backend."
                               "".format(fortrantype, symbol.name))
        if fortrantype == 'real' and precision not in [4, 8, 16]:
            raise VisitorError(
                "Datatype 'real' in symbol '{0}' supports fixed precision of "
                "[4, 8, 16] but found '{1}'.".format(symbol.name, precision))
        if fortrantype in ['integer', 'logical'] and precision not in \
           [1, 2, 4, 8, 16]:
            raise VisitorError(
                "Datatype '{0}' in symbol '{1}' supports fixed precision of "
                "[1, 2, 4, 8, 16] but found '{2}'."
                "".format(fortrantype, symbol.name, precision))
        # Precision has an an explicit size. Use the "type*size" Fortran
        # extension for simplicity. We could have used
        # type(kind=selected_int|real_kind(size)) or, for Fortran 2008,
        # ISO_FORTRAN_ENV; type(type64) :: MyType.
        return "{0}*{1}".format(fortrantype, precision)

    if isinstance(precision, ScalarType.Precision):
        # The precision information is not absolute so is either
        # machine specific or is specified via the compiler. Fortran
        # only distinguishes relative precision for single and double
        # precision reals.
        if fortrantype.lower() == "real" and \
           precision == ScalarType.Precision.DOUBLE:
            return "double precision"
        # This logging warning can be added when issue #11 is
        # addressed.
        # import logging
        # logging.warning(
        #      "Fortran does not support relative precision for the '%s' "
        #      "datatype but '%s' was specified for variable '%s'.",
        #      datatype, str(symbol.precision), symbol.name)
        return fortrantype

    if isinstance(precision, DataSymbol):
        if fortrantype not in ["real", "integer", "logical"]:
            raise VisitorError(
                "kind not supported for datatype '{0}' in symbol '{1}' in "
                "Fortran backend.".format(fortrantype, symbol.name))
        # The precision information is provided by a parameter, so use KIND.
        return "{0}(kind={1})".format(fortrantype, precision.name)

    raise VisitorError(
        "Unsupported precision type '{0}' found for symbol '{1}' in Fortran "
        "backend.".format(type(precision).__name__, symbol.name))