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
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
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
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
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
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
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
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
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)
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))
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.")
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))
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
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
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))
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))
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
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))
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
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))
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
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
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]))
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]))
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
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))