diff --git a/changelog b/changelog index 060343dfb9..e447e9f16a 100644 --- a/changelog +++ b/changelog @@ -530,6 +530,10 @@ 179) PR #2230 for #2228. Improve reporting of errors due to kernel functors not explicitly included in algorithm use statements. + 180) PR #2207 for #1419. Split the DataSymbol constant_value attribute + into is_constant (bool) and initial_value (PSyIR Node). This allows + parsing Fortran declarations with initial_value that are not constant. + release 2.3.1 17th of June 2022 1) PR #1747 for #1720. Adds support for If blocks to PSyAD. diff --git a/doc/developer_guide/psyir_symbols.rst b/doc/developer_guide/psyir_symbols.rst index d1cb967791..96d5ab5ad5 100644 --- a/doc/developer_guide/psyir_symbols.rst +++ b/doc/developer_guide/psyir_symbols.rst @@ -230,7 +230,8 @@ the subclass. For example: Sometimes providing additional properties of the new sub-class is desirable, and sometimes even mandatory (e.g. a `DataSymbol` must always have a datatype -and optionally a constant_value parameter). For this reason the specialise +and optionally is_constant and initial_value parameters). For this reason +the specialise method implementation provides the same interface as the constructor of the symbol type in order to provide the same behaviour and default values as the constructor. For instance, in the `DataSymbol` case the following @@ -241,15 +242,21 @@ specialisations are possible: >>> sym = Symbol("a") >>> # The following statement would fail because it doesn't have a datatype >>> # sym.specialise(DataSymbol) - >>> # The following statement is valid and constant_value is set to None + >>> # The following statement is valid (in this case initial_value will + >>> # default to None and is_constant to False): >>> sym.specialise(DataSymbol, datatype=INTEGER_TYPE) >>> sym2 = Symbol("b") - >>> # The following statement would fail because the constant_value doesn't - >>> # match the datatype of the symbol - >>> # sym2.specialise(DataSymbol, datatype=INTEGER_TYPE, constant_value=3.14) - >>> # The following statement is valid and constant_value is set to 3 - >>> sym2.specialise(DataSymbol, datatype=INTEGER_TYPE, constant_value=3) + >>> # The following statement would fail because the initial_value doesn't + >>> # match the datatype of the symbol: + >>> # sym2.specialise(DataSymbol, datatype=INTEGER_TYPE, initial_value=3.14) + >>> # The following statement is valid and initial_value is set to 3 + >>> # (and is_constant will default to False): + >>> sym2.specialise(DataSymbol, datatype=INTEGER_TYPE, initial_value=3) + >>> print(sym2.initial_value) + Literal[value:'3', Scalar] + >>> print(sym2.is_constant) + False Routine Interfaces diff --git a/doc/user_guide/psyir.rst b/doc/user_guide/psyir.rst index 86dae04ef6..2d2dd48903 100644 --- a/doc/user_guide/psyir.rst +++ b/doc/user_guide/psyir.rst @@ -257,7 +257,7 @@ example: >>> int_type = ScalarType(ScalarType.Intrinsic.INTEGER, ... ScalarType.Precision.SINGLE) >>> bool_type = ScalarType(ScalarType.Intrinsic.BOOLEAN, 4) - >>> symbol = DataSymbol("rdef", int_type, constant_value=4) + >>> symbol = DataSymbol("rdef", int_type, initial_value=4) >>> scalar_type = ScalarType(ScalarType.Intrinsic.REAL, symbol) For convenience PSyclone predefines a number of scalar datatypes: @@ -522,10 +522,12 @@ as keyword arguments. For example, the following code: symbol_table.new_symbol(root_name="something", symbol_type=DataSymbol, datatype=REAL_TYPE, - constant_value=3) + is_constant=True, + initial_value=3) declares a symbol named "something" of REAL_TYPE datatype where the -constant_value argument will be passed to the DataSymbol constructor. +``is_constant`` and ``initial_value`` arguments will be passed to the +DataSymbol constructor. An example of using the ``new_symbol()`` method can be found in the PSyclone ``examples/psyir`` directory. diff --git a/examples/psyir/create.py b/examples/psyir/create.py index 8ea5c6d9bd..ba62ccb138 100644 --- a/examples/psyir/create.py +++ b/examples/psyir/create.py @@ -46,7 +46,6 @@ C representation of the PSyIR. ''' -from __future__ import print_function from psyclone.psyir.nodes import Reference, Literal, UnaryOperation, \ BinaryOperation, NaryOperation, Assignment, IfBlock, Loop, \ Container, ArrayReference, Call, Routine, FileContainer @@ -80,7 +79,8 @@ def create_psyir_tree(): real_kind = symbol_table.new_symbol(root_name="RKIND", symbol_type=DataSymbol, datatype=INTEGER_TYPE, - constant_value=8) + is_constant=True, + initial_value=8) routine_symbol = RoutineSymbol("my_sub") # Array using precision defined by another symbol diff --git a/examples/psyir/create_structure_types.py b/examples/psyir/create_structure_types.py index eb0786378e..ee0860bb2a 100644 --- a/examples/psyir/create_structure_types.py +++ b/examples/psyir/create_structure_types.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council +# Copyright (c) 2020-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -43,7 +43,6 @@ >>> python create_structure_types.py ''' -from __future__ import print_function from psyclone.psyir.nodes import Literal, KernelSchedule, Container, \ StructureReference, ArrayOfStructuresReference, Assignment, \ BinaryOperation, Range @@ -58,7 +57,7 @@ CONTAINER_SYMBOL_TABLE = SymbolTable() REAL_KIND = CONTAINER_SYMBOL_TABLE.new_symbol( root_name="RKIND", symbol_type=DataSymbol, datatype=INTEGER_TYPE, - constant_value=8) + is_constant=True, initial_value=8) # Shorthand for a scalar type with REAL_KIND precision SCALAR_TYPE = ScalarType(ScalarType.Intrinsic.REAL, REAL_KIND) diff --git a/psyclone.pdf b/psyclone.pdf index 289e1bd762..2843c50031 100644 Binary files a/psyclone.pdf and b/psyclone.pdf differ diff --git a/src/psyclone/psyad/domain/common/adjoint_utils.py b/src/psyclone/psyad/domain/common/adjoint_utils.py index 7647f6efb7..84b1bc4d11 100644 --- a/src/psyclone/psyad/domain/common/adjoint_utils.py +++ b/src/psyclone/psyad/domain/common/adjoint_utils.py @@ -100,7 +100,8 @@ def create_real_comparison(sym_table, kernel, var1, var2): overall_tol = sym_table.new_symbol("overall_tolerance", symbol_type=DataSymbol, datatype=var1.datatype, - constant_value=INNER_PRODUCT_TOLERANCE) + is_constant=True, + initial_value=INNER_PRODUCT_TOLERANCE) # TODO #1161 - the PSyIR does not support `SPACING` assign = freader.psyir_from_statement( f"MachineTol = SPACING ( MAX( ABS({var1.name}), ABS({var2.name}) ) )", diff --git a/src/psyclone/psyad/tl2ad.py b/src/psyclone/psyad/tl2ad.py index cde28bf01d..d51a891f97 100644 --- a/src/psyclone/psyad/tl2ad.py +++ b/src/psyclone/psyad/tl2ad.py @@ -279,9 +279,9 @@ def _add_precision_symbol(symbol, table): if symbol.name in table: return - if symbol.is_automatic or symbol.is_modulevar: - table.add(symbol.copy()) - elif symbol.is_import: + if symbol.is_import: + # Handle imported symbols first because they may also be constants + # while the reverse is not true. contr_sym = symbol.interface.container_symbol try: kind_contr_sym = table.lookup(contr_sym.name) @@ -292,6 +292,8 @@ def _add_precision_symbol(symbol, table): kind_symbol = symbol.copy() kind_symbol.interface = ImportInterface(kind_contr_sym) table.add(kind_symbol) + elif symbol.is_automatic or symbol.is_modulevar or symbol.is_constant: + table.add(symbol.copy()) else: raise NotImplementedError( f"One or more variables have a precision specified by symbol " @@ -396,7 +398,8 @@ def generate_adjoint_test(tl_psyir, ad_psyir, dim_size_sym = symbol_table.new_symbol("array_extent", symbol_type=DataSymbol, datatype=INTEGER_TYPE, - constant_value=TEST_ARRAY_DIM_SIZE) + is_constant=True, + initial_value=TEST_ARRAY_DIM_SIZE) # Create symbols for the results of the inner products inner1 = symbol_table.new_symbol("inner1", symbol_type=DataSymbol, @@ -439,7 +442,8 @@ def generate_adjoint_test(tl_psyir, ad_psyir, new_dim_args_map[arg] = symbol_table.new_symbol( arg.name, symbol_type=DataSymbol, datatype=arg.datatype, - constant_value=Reference(dim_size_sym)) + is_constant=True, + initial_value=Reference(dim_size_sym)) # Create necessary variables for the kernel arguments. inputs = [] diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py index d4e97ea0ea..5d4c134c20 100644 --- a/src/psyclone/psyir/backend/fortran.py +++ b/src/psyclone/psyir/backend/fortran.py @@ -553,15 +553,17 @@ def gen_vardecl(self, symbol, include_visibility=False): :rtype: str :raises VisitorError: if the symbol is of DeferredType. - :raises VisitorError: if the symbol is of UnknownType other than \ + :raises VisitorError: if the symbol is of UnknownType other than UnknownFortranType. - :raises VisitorError: if the symbol is of known type but does not \ - specify a variable declaration (it is not a local declaration or \ + :raises VisitorError: if the symbol is of known type but does not + specify a variable declaration (it is not a local declaration or an argument declaration). + :raises VisitorError: if the symbol is a runtime constant but does not + have a StaticInterface. :raises InternalError: if the symbol is a ContainerSymbol or an import. - :raises InternalError: if the symbol is a RoutineSymbol other than \ + :raises InternalError: if the symbol is a RoutineSymbol other than UnknownFortranType. - :raises InternalError: if visibility is to be included but is not \ + :raises InternalError: if visibility is to be included but is not either PUBLIC or PRIVATE. ''' @@ -644,9 +646,15 @@ def gen_vardecl(self, symbol, include_visibility=False): # Specify name result += f" :: {symbol.name}" - # Specify initialization expression - if isinstance(symbol, DataSymbol) and symbol.is_constant: - result += " = " + self._visit(symbol.constant_value) + # Specify initialisation expression + if isinstance(symbol, DataSymbol) and symbol.initial_value: + if not symbol.is_static: + raise VisitorError( + f"{type(symbol).__name__} '{symbol.name}' has an initial " + f"value ({self._visit(symbol.initial_value)}) and " + f"therefore (in Fortran) must have a StaticInterface. " + f"However it has an interface of '{symbol.interface}'.") + result += " = " + self._visit(symbol.initial_value) return result + "\n" @@ -848,10 +856,10 @@ def _gen_parameter_decls(self, symbol_table, is_module_scope=False): decln_inputs[symbol.name] = set() read_write_info = ReadWriteInfo() self._dep_tools.get_input_parameters(read_write_info, - symbol.constant_value) + symbol.initial_value) # The dependence analysis tools do not include symbols used to # define precision so check for those here. - for lit in symbol.constant_value.walk(Literal): + for lit in symbol.initial_value.walk(Literal): if isinstance(lit.datatype.precision, DataSymbol): read_write_info.add_read( Signature(lit.datatype.precision.name)) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 52dda26dc5..ce5c3a6150 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -1786,8 +1786,6 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None): a non-array declaration. :raises InternalError: if an array with defined extent has the \ allocatable attribute. - :raises NotImplementedError: if an initialisation expression is found \ - for a variable declaration. :raises NotImplementedError: if an unsupported initialisation \ expression is found for a parameter declaration. :raises NotImplementedError: if a character-length specification is \ @@ -1891,7 +1889,7 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None): interface = DefaultModuleInterface() else: interface = AutomaticInterface() - # This might still be redifined as Argument later if it + # This might still be redefined as Argument later if it # appears in the argument list, but we don't know at this # point. @@ -1899,7 +1897,7 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None): # parent symbol table for each entity found. for entity in entities.items: (name, array_spec, char_len, initialisation) = entity.items - ct_expr = None + init_expr = None # If the entity has an array-spec shape, it has priority. # Otherwise use the declaration attribute shape. @@ -1931,19 +1929,12 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None): f"extent cannot have the ALLOCATABLE attribute.") if initialisation: - if has_constant_value: - # If it is a parameter parse its initialization into - # a dummy Assignment (but connected to the current scope - # since symbols must be resolved) - dummynode = Assignment(parent=scope) - expr = initialisation.items[1] - self.process_nodes(parent=dummynode, nodes=[expr]) - ct_expr = dummynode.children[0].detach() - else: - raise NotImplementedError( - f"Could not process {decl.items}. Initialisations on " - f"the declaration statements are only supported for " - f"parameter declarations.") + # If the variable or parameter has an initial value then + # parse its initialization into a dummy Assignment. + dummynode = Assignment(parent=scope) + expr = initialisation.items[1] + self.process_nodes(parent=dummynode, nodes=[expr]) + init_expr = dummynode.children[0].detach() if char_len is not None: raise NotImplementedError( @@ -1990,7 +1981,8 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None): try: sym = DataSymbol(sym_name, datatype, visibility=visibility, - constant_value=ct_expr) + is_constant=has_constant_value, + initial_value=init_expr) except ValueError: # Error setting initial value have to be raised as # NotImplementedError in order to create an UnknownType @@ -2009,7 +2001,14 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None): # We use copies of the interface object because we will reuse the # interface for each entity if there are multiple in the same # declaration statement. - sym.interface = interface.copy() + if init_expr: + # In Fortran, an initialisation expression on a declaration of + # a symbol (whether in a routine or a module) implies that the + # symbol is static (endures for the lifetime of the program) + # unless it is a pointer initialisation. + sym.interface = StaticInterface() + else: + sym.interface = interface.copy() def _process_derived_type_decln(self, parent, decl, visibility_map): ''' @@ -2123,27 +2122,23 @@ def _get_partial_datatype(self, node, scope, visibility_map): :type visibility_map: dict with str keys and values of type :py:class:`psyclone.psyir.symbols.Symbol.Visibility` - :returns: a PSyIR datatype, or datatype symbol, containing - partial datatype information for the declaration statement - in the cases where it is possible to extract this - information and None otherwise. - :rtype: Optional[:py:class:`psyclone.psyir.symbols.DataType` or - :py:class:`psyclone.psyir.symbols.DataTypeSymbol`] + :returns: a 2-tuple containing a PSyIR datatype, or datatype symbol, + containing partial datatype information for the declaration + statement and the PSyIR for any initialisation expression. + When it is not possible to extract partial datatype information + then (None, None) is returned. + :rtype: Tuple[ + Optional[:py:class:`psyclone.psyir.symbols.DataType` | + :py:class:`psyclone.psyir.symbols.DataTypeSymbol`], + Optional[:py:class:`psyclone.psyir.nodes.Node`]] ''' - # 1: Remove any initialisation and additional variables. TODO: - # This won't be needed when #1419 is implemented (assuming the - # implementation supports both assignments and pointer - # assignments). + # 1: Remove any additional variables. entity_decl_list = node.children[2] orig_entity_decl_list = list(entity_decl_list.children[:]) entity_decl_list.items = tuple(entity_decl_list.children[0:1]) entity_decl = entity_decl_list.children[0] orig_entity_decl_children = list(entity_decl.children[:]) - if isinstance(entity_decl.children[3], Fortran2003.Initialization): - entity_decl.items = ( - entity_decl.items[0], entity_decl.items[1], - entity_decl.items[2], None) # 2: Remove any unsupported attributes unsupported_attribute_names = ["pointer", "target"] @@ -2168,9 +2163,12 @@ def _get_partial_datatype(self, node, scope, visibility_map): visibility_map) symbol_name = node.children[2].children[0].children[0].string symbol_name = symbol_name.lower() - datatype = symbol_table.lookup(symbol_name).datatype + new_sym = symbol_table.lookup(symbol_name) + datatype = new_sym.datatype + init_expr = new_sym.initial_value except NotImplementedError: datatype = None + init_expr = None # Restore the fparser2 parse tree node.items = tuple(orig_node_children) @@ -2179,7 +2177,7 @@ def _get_partial_datatype(self, node, scope, visibility_map): node.children[2].items = tuple(orig_entity_decl_list) node.children[2].children[0].items = tuple(orig_entity_decl_children) - return datatype + return datatype, init_expr def process_declarations(self, parent, nodes, arg_list, visibility_map=None): @@ -2331,7 +2329,7 @@ def process_declarations(self, parent, nodes, arg_list, pass # Try to extract partial datatype information. - datatype = self._get_partial_datatype( + datatype, init = self._get_partial_datatype( node, parent, visibility_map) # If a declaration declares multiple entities, it's @@ -2344,7 +2342,8 @@ def process_declarations(self, parent, nodes, arg_list, str(node), partial_datatype=datatype), interface=UnknownInterface(), - visibility=vis), + visibility=vis, + initial_value=init), tag=tag) except KeyError as err: @@ -2397,7 +2396,10 @@ def process_declarations(self, parent, nodes, arg_list, # Add the initialization expression in the symbol # constant_value attribute ct_expr = dummynode.children[0].detach() - symbol.constant_value = ct_expr + symbol.initial_value = ct_expr + symbol.is_constant = True + # Ensure the interface to this Symbol is static + symbol.interface = StaticInterface() else: # TODO #1254: We currently silently ignore the rest of # the Implicit_Part statements @@ -2508,12 +2510,15 @@ def _process_common_blocks(nodes, psyir_parent): :param nodes: fparser2 AST nodes containing declaration statements. :type nodes: List[:py:class:`fparser.two.utils.Base`] - :param psyir_parent: the PSyIR Node with a symbol table in which to \ + :param psyir_parent: the PSyIR Node with a symbol table in which to add the Common Blocks and update the symbols interfaces. :type psyir_parent: :py:class:`psyclone.psyir.nodes.ScopingNode` - :raises NotImplementedError: if it is unable to find one of the \ - CommonBlock expressions in the symbol table (because it has not \ + :raises NotImplementedError: if one of the Symbols in a common block + has initialisation (including when it is a parameter). This is not + valid Fortran. + :raises NotImplementedError: if it is unable to find one of the + CommonBlock expressions in the symbol table (because it has not been declared yet or when it is not just the symbol name). ''' @@ -2538,6 +2543,13 @@ def _process_common_blocks(nodes, psyir_parent): for symbol_name in cb_object[1].items: sym = psyir_parent.symbol_table.lookup( str(symbol_name)) + if sym.initial_value: + # This is C506 of the F2008 standard. + raise NotImplementedError( + f"Symbol '{sym.name}' has an initial value" + f" ({sym.initial_value.debug_string()}) " + f"but appears in a common block. This is " + f"not valid Fortran.") sym.interface = CommonBlockInterface() except KeyError as error: raise NotImplementedError( diff --git a/src/psyclone/psyir/symbols/datasymbol.py b/src/psyclone/psyir/symbols/datasymbol.py index 2671332591..505e1b93b2 100644 --- a/src/psyclone/psyir/symbols/datasymbol.py +++ b/src/psyclone/psyir/symbols/datasymbol.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2017-2022, Science and Technology Facilities Council. +# Copyright (c) 2017-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -38,8 +38,8 @@ ''' This module contains the DataSymbol and its interfaces.''' -from __future__ import absolute_import from psyclone.psyir.symbols.typed_symbol import TypedSymbol +from psyclone.psyir.symbols.interfaces import StaticInterface class DataSymbol(TypedSymbol): @@ -51,126 +51,196 @@ class DataSymbol(TypedSymbol): :param str name: name of the symbol. :param datatype: data type of the symbol. :type datatype: :py:class:`psyclone.psyir.symbols.DataType` - :param constant_value: sets a fixed known expression as a permanent \ - value for this DataSymbol. If the value is None then this \ - symbol does not have a fixed constant. Otherwise it can receive \ - PSyIR expressions or Python intrinsic types available in the \ - TYPE_MAP_TO_PYTHON map. By default it is None. - :type constant_value: NoneType, item of TYPE_MAP_TO_PYTHON or \ - :py:class:`psyclone.psyir.nodes.Node` - :param kwargs: additional keyword arguments provided by \ + :param bool is_constant: whether this DataSymbol is a compile-time + constant (default is False). If True then an `initial_value` must + also be provided. + :param initial_value: sets a fixed known expression as an initial + value for this DataSymbol. If `is_constant` is True then this + Symbol will always have this value. If the value is None then this + symbol does not have an initial value (and cannot be a constant). + Otherwise it can receive PSyIR expressions or Python intrinsic types + available in the TYPE_MAP_TO_PYTHON map. By default it is None. + :type initial_value: Optional[item of TYPE_MAP_TO_PYTHON | + :py:class:`psyclone.psyir.nodes.Node`] + :param kwargs: additional keyword arguments provided by :py:class:`psyclone.psyir.symbols.TypedSymbol` :type kwargs: unwrapped dict. ''' - def __init__(self, name, datatype, constant_value=None, **kwargs): - super(DataSymbol, self).__init__(name, datatype) - self._constant_value = None - self._process_arguments(constant_value=constant_value, + def __init__(self, name, datatype, is_constant=False, initial_value=None, + **kwargs): + super().__init__(name, datatype) + self._is_constant = False + self._initial_value = None + self._process_arguments(is_constant=is_constant, + initial_value=initial_value, **kwargs) def _process_arguments(self, **kwargs): ''' Process the arguments for the constructor and the specialise - methods. In this case the constant_value argument. + methods. In this case the initial_value and is_constant arguments. :param kwargs: keyword arguments which can be:\n - :param constant_value: sets a fixed known expression as a \ - permanent value for this DataSymbol. If the value is None \ - then this symbol does not have a fixed constant. Otherwise \ - it can receive PSyIR expressions or Python intrinsic types \ - available in the TYPE_MAP_TO_PYTHON map. By default it is \ - set to None. \n - :type constant_value: NoneType, item of TYPE_MAP_TO_PYTHON or \ - :py:class:`psyclone.psyir.nodes.Node`\n + :param bool is_constant: whether this DataSymbol is a compile-time + constant (default is False). If True then an `initial_value` + must also be provided.\n + :param initial_value: sets a fixed known expression as an initial + value for this DataSymbol. If `is_constant` is True then this + Symbol will always have this value. If the value is None then + this symbol does not have an initial value (and cannot be a + constant). Otherwise it can receive PSyIR expressions or Python + intrinsic types available in the TYPE_MAP_TO_PYTHON map. By + default it is None.\n + :type initial_value: Optional[item of TYPE_MAP_TO_PYTHON | + :py:class:`psyclone.psyir.nodes.Node`]\n and the arguments in :py:class:`psyclone.psyir.symbols.TypedSymbol` :type kwargs: unwrapped dict. + :raises ValueError: if the symbol is a run-time constant but is not + given an initial value. + :raises ValueError: if the symbol is a run-time constant and an + interface other than StaticInterface is specified. ''' - new_constant_value = None - if "constant_value" in kwargs: - new_constant_value = kwargs.pop("constant_value") - elif not hasattr(self, '_constant_value'): + new_initial_value = None + new_is_constant_value = None + + # We need to consume 'initial_value' and 'is_constant' before calling + # the super because otherwise there will be an unknown argument in + # kwargs. However, we can only call the 'initial_value' setter after + # the super because it uses self.datatype which in turn is set in + # the super. + + if "initial_value" in kwargs: + new_initial_value = kwargs.pop("initial_value") + elif not hasattr(self, '_initial_value'): + # Initialise this attribute if we reach this point and this object + # doesn't already have it. + self._initial_value = None + + if "is_constant" in kwargs: + new_is_constant_value = kwargs.pop("is_constant") + elif not hasattr(self, '_is_constant'): # At least initialise it if we reach this point and it doesn't # exist - self._constant_value = None + self._is_constant = False + + # Record whether an explicit value has been supplied for 'interface' + interface_supplied = "interface" in kwargs - # We need to consume the 'constant_value' before calling the super - # because otherwise there will be an unknown argument in kwargs but - # we need to call the 'constant_value' setter after this because it - # uses the self.datatype which is in turn set in the super. - super(DataSymbol, self)._process_arguments(**kwargs) + super()._process_arguments(**kwargs) - # Now that we have a datatype we can use the constant_value setter - # with proper error checking - if new_constant_value: - self.constant_value = new_constant_value + # Now that we have a datatype we can use initial_value setter + # with proper error checking. + if new_initial_value is not None: + self.initial_value = new_initial_value + + # Now that we know whether or not we have an intial_value, we can + # call the is_constant setter. + if new_is_constant_value is not None: + self.is_constant = new_is_constant_value + + # A run-time constant must have a StaticInterface or an + # ImportInterface. If the user did not supply an explicit interface + # then default to StaticInterface. If they did supply + # one then we check it is valid. + if self.is_constant: + if interface_supplied: + if not (self.is_static or self.is_import): + raise ValueError( + f"A DataSymbol representing a constant must have " + f"either a StaticInterface or an ImportInterface but " + f"'{self.name}' has interface '{self.interface}'.") + else: + # No explicit interface was supplied and this Symbol represents + # a runtime constant so change its interface to be static. + self.interface = StaticInterface() @property def is_constant(self): ''' - :returns: Whether the symbol is a constant with a fixed known \ - value (True) or not (False). + :returns: Whether the symbol is a compile-time constant (True) or + not (False). :rtype: bool + ''' + return self._is_constant + @is_constant.setter + def is_constant(self, value): ''' - return self._constant_value is not None + :param bool value: whether or not this symbol is a compile-time + constant. + + :raises ValueError: if `value` is True but this symbol does not have an + initial value set and does not have an ImportInterface. + + ''' + if value and not self.is_import and self.initial_value is None: + raise ValueError( + f"DataSymbol '{self.name}' does not have an initial value set " + f"and is not imported and therefore cannot be a constant.") + self._is_constant = value @property - def constant_value(self): + def initial_value(self): ''' - :returns: the fixed known value of this symbol. + :returns: the initial value associated with this symbol (if any). :rtype: :py:class:`psyclone.psyir.nodes.Node` ''' - return self._constant_value + return self._initial_value - @constant_value.setter - def constant_value(self, new_value): + @initial_value.setter + def initial_value(self, new_value): ''' - :param new_value: set or change the fixed known value of the \ - constant for this DataSymbol. If the value is None then this \ - symbol does not have a fixed constant. Otherwise it can receive \ - PSyIR expressions or Python intrinsic types available in the \ - TYPE_MAP_TO_PYTHON map. - :type new_value: NoneType, item of TYPE_MAP_TO_PYTHON or \ - :py:class:`psyclone.psyir.nodes.Node` - - :raises ValueError: if a non-None value is provided and 1) this \ - DataSymbol instance does not have local scope, or 2) this \ - DataSymbol instance is not a scalar (as the shape attribute is \ - not empty), or 3) a constant value is provided but the type of \ - the value does is not supported, or 4) the type of the value \ - provided is not compatible with the datatype of this DataSymbol \ + :param new_value: set or change the initial value associated + with this DataSymbol. If the value is None then this symbol does + not have an initial value (and cannot be a constant). Otherwise it + can receive PSyIR expressions or Python intrinsic types available + in the TYPE_MAP_TO_PYTHON map. + :type new_value: Optional[item of TYPE_MAP_TO_PYTHON | + :py:class:`psyclone.psyir.nodes.Node`] + + :raises ValueError: if a non-None value is provided and 1) this + DataSymbol instance represents an argument, or 2) this + DataSymbol instance is not a scalar (as the shape attribute is + not empty), or 3) an initial value is provided but the type of + the value is not supported, or 4) the type of the value + provided is not compatible with the datatype of this DataSymbol instance, or 5) the provided PSyIR expression is unsupported. + :raises ValueError: if a None value is provided and this DataSymbol + represents a constant and is not imported. ''' # pylint: disable=import-outside-toplevel from psyclone.psyir.nodes import (Node, Literal, Operation, Reference, CodeBlock) - from psyclone.psyir.symbols.datatypes import ScalarType, ArrayType + from psyclone.psyir.symbols.datatypes import (ScalarType, ArrayType, + UnknownType) if new_value is not None: if self.is_argument: raise ValueError( - f"Error setting constant value for symbol '{self.name}'. " - f"A DataSymbol with an ArgumentInterface can not have a " - f"constant value.") - if not isinstance(self.datatype, (ScalarType, ArrayType)): + f"Error setting initial value for symbol '{self.name}'. " + f"A DataSymbol with an ArgumentInterface can not have an " + f"initial value.") + if not isinstance(self.datatype, + (ScalarType, ArrayType, UnknownType)): raise ValueError( - f"Error setting constant value for symbol '{self.name}'. " - f"A DataSymbol with a constant value must be a scalar or " - f"an array but found '{type(self.datatype).__name__}'.") + f"Error setting initial value for symbol '{self.name}'. " + f"A DataSymbol with an initial value must be a scalar or " + f"an array or of UnknownType but found " + f"'{type(self.datatype).__name__}'.") if isinstance(new_value, Node): for node in new_value.walk(Node): if not isinstance(node, (Literal, Operation, Reference, CodeBlock)): raise ValueError( - f"Error setting constant value for symbol " + f"Error setting initial value for symbol " f"'{self.name}'. PSyIR static expressions can only" f" contain PSyIR Literal, Operation, Reference or " f"CodeBlock nodes but found: {node}") - self._constant_value = new_value + self._initial_value = new_value else: from psyclone.psyir.symbols.datatypes import TYPE_MAP_TO_PYTHON # No need to check that self.datatype has an intrinsic @@ -179,30 +249,36 @@ def constant_value(self, new_value): lookup = TYPE_MAP_TO_PYTHON[self.datatype.intrinsic] if not isinstance(new_value, lookup): raise ValueError( - f"Error setting constant value for symbol " + f"Error setting initial value for symbol " f"'{self.name}'. This DataSymbol instance datatype is " - f"'{self.datatype}' meaning the constant value should " + f"'{self.datatype}' meaning the initial value should " f"be '{lookup}' but found '{type(new_value)}'.") if self.datatype.intrinsic == ScalarType.Intrinsic.BOOLEAN: # In this case we know new_value is a Python boolean as it # has passed the isinstance(new_value, lookup) check. if new_value: - self._constant_value = Literal('true', self.datatype) + self._initial_value = Literal('true', self.datatype) else: - self._constant_value = Literal('false', self.datatype) + self._initial_value = Literal('false', self.datatype) else: # Otherwise we convert the Python intrinsic to a PSyIR # Literal using its string representation. - self._constant_value = Literal(str(new_value), - self.datatype) + self._initial_value = Literal(str(new_value), + self.datatype) else: - self._constant_value = None + if self.is_constant and not self.is_import: + raise ValueError( + f"DataSymbol '{self.name}' is a constant and not imported " + f"and therefore must have an initial value but got None") + self._initial_value = None def __str__(self): ret = self.name + ": DataSymbol<" + str(self.datatype) ret += ", " + str(self._interface) + if self.initial_value is not None: + ret += f", initial_value={self.initial_value}" if self.is_constant: - ret += f", constant_value={self.constant_value}" + ret += ", constant=True" return ret + ">" def copy(self): @@ -216,7 +292,8 @@ def copy(self): ''' return DataSymbol(self.name, self.datatype, visibility=self.visibility, interface=self.interface, - constant_value=self.constant_value) + is_constant=self.is_constant, + initial_value=self.initial_value) def copy_properties(self, symbol_in): '''Replace all properties in this object with the properties from @@ -231,5 +308,6 @@ def copy_properties(self, symbol_in): if not isinstance(symbol_in, DataSymbol): raise TypeError(f"Argument should be of type 'DataSymbol' but " f"found '{type(symbol_in).__name__}'.") - super(DataSymbol, self).copy_properties(symbol_in) - self._constant_value = symbol_in.constant_value + super().copy_properties(symbol_in) + self._is_constant = symbol_in.is_constant + self._initial_value = symbol_in.initial_value diff --git a/src/psyclone/psyir/transformations/inline_trans.py b/src/psyclone/psyir/transformations/inline_trans.py index cb40bf7b55..3e5a3ec0a4 100644 --- a/src/psyclone/psyir/transformations/inline_trans.py +++ b/src/psyclone/psyir/transformations/inline_trans.py @@ -41,8 +41,8 @@ from psyclone.psyGen import Transformation from psyclone.psyir.nodes import ( ArrayReference, ArrayOfStructuresReference, BinaryOperation, Call, - CodeBlock, Container, IntrinsicCall, Range, Routine, Reference, Return, - Literal, Assignment, StructureMember, StructureReference) + CodeBlock, Container, IntrinsicCall, Node, Range, Routine, Reference, + Return, Literal, Assignment, StructureMember, StructureReference) from psyclone.psyir.nodes.array_mixin import ArrayMixin from psyclone.psyir.symbols import ( ArgumentInterface, ArrayType, DataSymbol, DeferredType, INTEGER_TYPE, @@ -650,7 +650,8 @@ def validate(self, node, options=None): f" '{sym.datatype.declaration}'") # Check that there are no static variables in the routine (because # we don't know whether the routine is called from other places). - if isinstance(sym.interface, StaticInterface): + if (isinstance(sym.interface, StaticInterface) and + not sym.is_constant): raise TransformationError( f"Routine '{routine.name}' cannot be inlined because it " f"has a static (Fortran SAVE) interface for Symbol " @@ -674,16 +675,21 @@ def validate(self, node, options=None): # table. If a precision symbol is only used within Statements then we # don't currently capture the fact that it is a precision symbol. ref_or_lits = routine.walk((Reference, Literal)) - # Check for symbols in any constant-value expressions - # (Fortran parameters) or array dimensions. - for sym in routine_table.automatic_datasymbols: - if sym.is_constant: + # Check for symbols in any initial-value expressions + # (including Fortran parameters) or array dimensions. + for sym in routine_table.datasymbols: + if sym.initial_value: ref_or_lits.extend( - sym.constant_value.walk((Reference, Literal))) + sym.initial_value.walk((Reference, Literal))) if isinstance(sym.datatype, ArrayType): for dim in sym.shape: - ref_or_lits.extend(dim.lower.walk(Reference, Literal)) - ref_or_lits.extend(dim.upper.walk(Reference, Literal)) + if isinstance(dim, ArrayType.ArrayBounds): + if isinstance(dim.lower, Node): + ref_or_lits.extend(dim.lower.walk(Reference, + Literal)) + if isinstance(dim.upper, Node): + ref_or_lits.extend(dim.upper.walk(Reference, + Literal)) # Keep a reference to each Symbol that we check so that we can avoid # repeatedly checking the same Symbol. _symbol_cache = set() diff --git a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py index a7b60183bd..15e4ec6c1e 100644 --- a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py @@ -280,7 +280,7 @@ def apply(self, node, options=None): dimension_literal = dimension_ref elif (isinstance(dimension_ref, Reference) and dimension_ref.symbol.is_constant): - dimension_literal = dimension_ref.symbol.constant_value + dimension_literal = dimension_ref.symbol.initial_value # else exception is handled by the validate method. # Determine the dimension and extent of the array diff --git a/src/psyclone/tests/domain/gocean/transformations/globalstoargs_test.py b/src/psyclone/tests/domain/gocean/transformations/globalstoargs_test.py index b4e2240b5c..d3b571c962 100644 --- a/src/psyclone/tests/domain/gocean/transformations/globalstoargs_test.py +++ b/src/psyclone/tests/domain/gocean/transformations/globalstoargs_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2017-2022, Science and Technology Facilities Council. +# Copyright (c) 2017-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -37,7 +37,6 @@ ''' Tests the KernelImportsToArguments Transformation for the GOcean 1.0 API.''' -from __future__ import absolute_import, print_function import os import pytest from psyclone.parse.algorithm import parse @@ -210,7 +209,8 @@ def test_kernelimportstoargumentstrans_constant(monkeypatch): def create_data_symbol(arg): symbol = DataSymbol(arg.name, INTEGER_TYPE, interface=arg.interface, - constant_value=Literal("1", INTEGER_TYPE)) + is_constant=True, + initial_value=Literal("1", INTEGER_TYPE)) return symbol monkeypatch.setattr(DataSymbol, "resolve_deferred", create_data_symbol) diff --git a/src/psyclone/tests/domain/gocean/transformations/gocean_opencl_trans_test.py b/src/psyclone/tests/domain/gocean/transformations/gocean_opencl_trans_test.py index 1ae320a10c..b0446423e2 100644 --- a/src/psyclone/tests/domain/gocean/transformations/gocean_opencl_trans_test.py +++ b/src/psyclone/tests/domain/gocean/transformations/gocean_opencl_trans_test.py @@ -551,21 +551,21 @@ def test_psy_init_defaults(kernel_outputdir): otrans.apply(sched) generated_code = str(psy.gen) expected = ''' - SUBROUTINE psy_init() - USE fortcl, ONLY: add_kernels, ocl_env_init - CHARACTER(LEN=30) kernel_names(1) - INTEGER :: ocl_device_num = 1 - LOGICAL, SAVE :: initialised = .FALSE. + subroutine psy_init() + use fortcl, only: add_kernels, ocl_env_init + character(len=30) kernel_names(1) + integer, save :: ocl_device_num = 1 + logical, save :: initialised = .false. - IF (.NOT.initialised) THEN + if (.not.initialised) then initialised = .true. - CALL ocl_env_init(1, ocl_device_num, .false., .false.) + call ocl_env_init(1, ocl_device_num, .false., .false.) kernel_names(1) = 'compute_cu_code' - CALL add_kernels(1, kernel_names) - END IF + call add_kernels(1, kernel_names) + end if - END SUBROUTINE psy_init''' - assert expected in generated_code + end subroutine psy_init''' + assert expected in generated_code.lower() assert GOceanOpenCLBuild(kernel_outputdir).code_compiles(psy) @@ -628,23 +628,23 @@ def test_psy_init_multiple_devices_per_node(kernel_outputdir, monkeypatch): generated_code = str(psy.gen) expected = ''' - SUBROUTINE psy_init() - USE parallel_mod, ONLY: get_rank - USE fortcl, ONLY: add_kernels, ocl_env_init - CHARACTER(LEN=30) kernel_names(1) - INTEGER :: ocl_device_num = 1 - LOGICAL, SAVE :: initialised = .FALSE. - - IF (.NOT.initialised) THEN + subroutine psy_init() + use parallel_mod, only: get_rank + use fortcl, only: add_kernels, ocl_env_init + character(len=30) kernel_names(1) + integer, save :: ocl_device_num = 1 + logical, save :: initialised = .false. + + if (.not.initialised) then initialised = .true. - ocl_device_num = MOD(get_rank() - 1, 2) + 1 - CALL ocl_env_init(1, ocl_device_num, .false., .false.) + ocl_device_num = mod(get_rank() - 1, 2) + 1 + call ocl_env_init(1, ocl_device_num, .false., .false.) kernel_names(1) = 'compute_cu_code' - CALL add_kernels(1, kernel_names) - END IF + call add_kernels(1, kernel_names) + end if - END SUBROUTINE psy_init''' - assert expected in generated_code + end subroutine psy_init''' + assert expected in generated_code.lower() assert GOceanOpenCLBuild(kernel_outputdir).code_compiles(psy) diff --git a/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py b/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py index 8e8927ef0d..25b0f15066 100644 --- a/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py +++ b/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py @@ -7474,7 +7474,8 @@ def test_kern_const_invalid_make_constant2(): # Expecting scalar integer. Set to constant. symbol._datatype = ScalarType(ScalarType.Intrinsic.INTEGER, ScalarType.Precision.UNDEFINED) - symbol._constant_value = 10 + symbol._initial_value = Literal("10", INTEGER_TYPE) + symbol._is_constant = True with pytest.raises(TransformationError) as excinfo: kctrans.apply(kernel, {"element_order": 0}) assert ("Expected entry to be a scalar integer argument but found " diff --git a/src/psyclone/tests/psyad/transformations/test_assignment_trans.py b/src/psyclone/tests/psyad/transformations/test_assignment_trans.py index ea169ac99f..a9dd5546a2 100644 --- a/src/psyclone/tests/psyad/transformations/test_assignment_trans.py +++ b/src/psyclone/tests/psyad/transformations/test_assignment_trans.py @@ -952,7 +952,8 @@ def test_validate_rhs_zero(): assignment = Assignment.create(Reference(lhs_symbol), rhs_literal) trans.validate(assignment) # 0 with a kind value - real_kind = DataSymbol("r_def", INTEGER_TYPE, constant_value=8) + real_kind = DataSymbol("r_def", INTEGER_TYPE, is_constant=True, + initial_value=8) scalar_type = ScalarType(ScalarType.Intrinsic.REAL, real_kind) rhs_literal = Literal("0.0", scalar_type) assignment = Assignment.create(Reference(lhs_symbol), rhs_literal) diff --git a/src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py b/src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py index 5b30161ebe..875f554be0 100644 --- a/src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py +++ b/src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py @@ -52,12 +52,12 @@ def test_gen_param_decls_dependencies(fortran_writer): ''' Test that dependencies between parameter declarations are handled. ''' symbol_table = SymbolTable() - rlg_sym = DataSymbol("rlg", INTEGER_TYPE, - constant_value=Literal("8", INTEGER_TYPE)) - wp_sym = DataSymbol("wp", INTEGER_TYPE, - constant_value=Reference(rlg_sym)) - var_sym = DataSymbol("var", INTEGER_TYPE, - constant_value=BinaryOperation.create( + rlg_sym = DataSymbol("rlg", INTEGER_TYPE, is_constant=True, + initial_value=Literal("8", INTEGER_TYPE)) + wp_sym = DataSymbol("wp", INTEGER_TYPE, is_constant=True, + initial_value=Reference(rlg_sym)) + var_sym = DataSymbol("var", INTEGER_TYPE, is_constant=True, + initial_value=BinaryOperation.create( BinaryOperation.Operator.ADD, Reference(rlg_sym), Reference(wp_sym))) symbol_table.add(var_sym) @@ -70,8 +70,8 @@ def test_gen_param_decls_dependencies(fortran_writer): # Check that an (invalid, obviously) circular dependency is handled. # Replace "rlg" with a new one that depends on "wp". del symbol_table._symbols[rlg_sym.name] - rlg_sym = DataSymbol("rlg", INTEGER_TYPE, - constant_value=Reference(wp_sym)) + rlg_sym = DataSymbol("rlg", INTEGER_TYPE, is_constant=True, + initial_value=Reference(wp_sym)) symbol_table.add(rlg_sym) with pytest.raises(VisitorError) as err: fortran_writer._gen_parameter_decls(symbol_table) @@ -99,15 +99,15 @@ def test_gen_param_decls_kind_dep(fortran_writer): ''' Check that symbols defining precision are accounted for when allowing for dependencies between parameter declarations. ''' table = SymbolTable() - rdef_sym = DataSymbol("r_def", INTEGER_TYPE, - constant_value=Literal("4", INTEGER_TYPE)) - wp_sym = DataSymbol("wp", INTEGER_TYPE, - constant_value=Reference(rdef_sym)) + rdef_sym = DataSymbol("r_def", INTEGER_TYPE, is_constant=True, + initial_value=Literal("4", INTEGER_TYPE)) + wp_sym = DataSymbol("wp", INTEGER_TYPE, is_constant=True, + initial_value=Reference(rdef_sym)) rdef_type = ScalarType(ScalarType.Intrinsic.REAL, wp_sym) - var_sym = DataSymbol("var", rdef_type, - constant_value=Literal("1.0", rdef_type)) - var2_sym = DataSymbol("var2", REAL_TYPE, - constant_value=Literal("1.0", rdef_type)) + var_sym = DataSymbol("var", rdef_type, is_constant=True, + initial_value=Literal("1.0", rdef_type)) + var2_sym = DataSymbol("var2", REAL_TYPE, is_constant=True, + initial_value=Literal("1.0", rdef_type)) table.add(var2_sym) table.add(var_sym) table.add(wp_sym) @@ -144,8 +144,8 @@ def test_gen_decls(fortran_writer): symbol_table.add(grid_type) grid_variable = DataSymbol("grid", grid_type) symbol_table.add(grid_variable) - symbol_table.add(DataSymbol("rlg", INTEGER_TYPE, - constant_value=Literal("8", INTEGER_TYPE))) + symbol_table.add(DataSymbol("rlg", INTEGER_TYPE, is_constant=True, + initial_value=Literal("8", INTEGER_TYPE))) result = fortran_writer.gen_decls(symbol_table) # If derived type declaration is not inside a module then its components # cannot have accessibility attributes. @@ -277,7 +277,8 @@ def test_gen_decls_static_variables(fortran_writer): symbol_table.add(sym) assert "integer, save :: v1" in fortran_writer.gen_decls(symbol_table) assert "integer, save :: v1" in fortran_writer.gen_vardecl(sym) - sym.constant_value = 1 + sym.initial_value = 1 + sym.is_constant = True assert "parameter :: v1 = 1" in fortran_writer.gen_vardecl(sym) diff --git a/src/psyclone/tests/psyir/backend/fortran_test.py b/src/psyclone/tests/psyir/backend/fortran_test.py index 1e0e8939fc..abb283f70a 100644 --- a/src/psyclone/tests/psyir/backend/fortran_test.py +++ b/src/psyclone/tests/psyir/backend/fortran_test.py @@ -51,11 +51,12 @@ Schedule, Routine, Return, FileContainer, IfBlock, OMPTaskloopDirective, OMPMasterDirective, OMPParallelDirective, Loop, OMPNumTasksClause, OMPDependClause, IntrinsicCall) -from psyclone.psyir.symbols import DataSymbol, SymbolTable, ContainerSymbol, \ - ImportInterface, ArgumentInterface, UnresolvedInterface, ScalarType, \ - ArrayType, INTEGER_TYPE, REAL_TYPE, CHARACTER_TYPE, BOOLEAN_TYPE, \ - REAL_DOUBLE_TYPE, DeferredType, RoutineSymbol, Symbol, UnknownType, \ - UnknownFortranType, DataTypeSymbol, StructureType +from psyclone.psyir.symbols import ( + DataSymbol, SymbolTable, ContainerSymbol, RoutineSymbol, Symbol, + ImportInterface, ArgumentInterface, UnresolvedInterface, StaticInterface, + ScalarType, ArrayType, INTEGER_TYPE, REAL_TYPE, CHARACTER_TYPE, + BOOLEAN_TYPE, REAL_DOUBLE_TYPE, DeferredType, + UnknownType, UnknownFortranType, DataTypeSymbol, StructureType) from psyclone.errors import InternalError from psyclone.tests.utilities import Compile from psyclone.psyGen import PSyFactory @@ -678,14 +679,28 @@ def test_fw_gen_vardecl(fortran_writer): interface=ArgumentInterface( ArgumentInterface.Access.READWRITE)) result = fortran_writer.gen_vardecl(symbol) - assert result == \ - "real, allocatable, dimension(:,:), intent(inout) :: dummy2\n" + assert (result == + "real, allocatable, dimension(:,:), intent(inout) :: dummy2\n") - # Constant - symbol = DataSymbol("dummy3", INTEGER_TYPE, constant_value=10) + # Constant. + symbol = DataSymbol("dummy3", INTEGER_TYPE, is_constant=True, + initial_value=10) result = fortran_writer.gen_vardecl(symbol) assert result == "integer, parameter :: dummy3 = 10\n" + # Symbol has initial value but is not constant (static). This is a property + # of the Fortran language and therefore is only checked for when we attempt + # to generate Fortran. + symbol = DataSymbol("dummy3a", INTEGER_TYPE, initial_value=10) + with pytest.raises(VisitorError) as err: + _ = fortran_writer.gen_vardecl(symbol) + assert ("'dummy3a' has an initial value (10) and therefore (in Fortran) " + "must have a StaticInterface. However it has an interface of " + "'Automatic'" in str(err.value)) + symbol.interface = StaticInterface() + result = fortran_writer.gen_vardecl(symbol) + assert result == "integer, save :: dummy3a = 10\n" + # Use statement symbol = DataSymbol("dummy1", DeferredType(), interface=ImportInterface( @@ -708,7 +723,8 @@ def test_fw_gen_vardecl_visibility(fortran_writer): ''' Test the include_visibility argument to gen_vardecl(). ''' # Simple constant symbol = DataSymbol("dummy3", INTEGER_TYPE, - visibility=Symbol.Visibility.PUBLIC, constant_value=10) + visibility=Symbol.Visibility.PUBLIC, is_constant=True, + initial_value=10) # Expect include_visibility to default to False result = fortran_writer.gen_vardecl(symbol) assert result == "integer, parameter :: dummy3 = 10\n" diff --git a/src/psyclone/tests/psyir/frontend/fparser2_common_block_test.py b/src/psyclone/tests/psyir/frontend/fparser2_common_block_test.py index c8d5bbbc11..9491bbac0d 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_common_block_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_common_block_test.py @@ -216,3 +216,23 @@ def test_commonblock_with_explicit_array_shape_symbol(): assert ("The symbol interface of a common block variable could not be " "updated because of \"Could not find 'a(10, 4)' in the Symbol " "Table.\"." in str(err.value)) + + +@pytest.mark.usefixtures("f2008_parser") +def test_commonblock_with_explicit_init_symbol(): + ''' Test that commonblocks containing a symbol declared with explicit + initialisation produce NotImplementedError.''' + + # Create a dummy test routine + routine = Routine("test_routine") + processor = Fparser2Reader() + + # This is also invalid Fortran, but fparser2 doesn't notice. + reader = FortranStringReader(''' + integer :: a = 10 + common /name1/ a''') + fparser2spec = Specification_Part(reader) + with pytest.raises(NotImplementedError) as err: + processor.process_declarations(routine, fparser2spec.content, []) + assert ("Symbol 'a' has an initial value (10) but appears in a common " + "block." in str(err.value)) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py b/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py index ba96c00a19..a1cd5f7a2f 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py @@ -32,6 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: S. Siso, STFC Daresbury Lab +# Modified: A. R. Porter, STFC Daresbury Lab # ----------------------------------------------------------------------------- ''' Tests Fortran parameter statements in the fparser2 PSyIR front-end ''' @@ -42,7 +43,7 @@ from psyclone.psyir.frontend.fparser2 import Fparser2Reader from psyclone.psyir.nodes import Routine, Literal, BinaryOperation, \ Container, CodeBlock, Reference -from psyclone.psyir.symbols import Symbol +from psyclone.psyir.symbols import Symbol, StaticInterface @pytest.mark.usefixtures("f2008_parser") @@ -63,8 +64,10 @@ def test_parameter_statements_work(): processor.process_declarations(routine, fparser2spec.content, []) newsymbol = symtab.lookup("var1") assert newsymbol.is_constant - assert isinstance(newsymbol.constant_value, Literal) - assert newsymbol.constant_value.value == "3" + assert isinstance(newsymbol.initial_value, Literal) + assert newsymbol.initial_value.value == "3" + assert newsymbol.is_constant is True + assert isinstance(newsymbol.interface, StaticInterface) # Test with a single parameter with an expression reader = FortranStringReader(''' @@ -74,8 +77,9 @@ def test_parameter_statements_work(): processor.process_declarations(routine, fparser2spec.content, []) newsymbol1 = symtab.lookup("var_expr") assert newsymbol1.is_constant - assert isinstance(newsymbol1.constant_value, BinaryOperation) - assert newsymbol1.constant_value.children[0].value == "10" + assert isinstance(newsymbol1.initial_value, BinaryOperation) + assert newsymbol1.initial_value.children[0].value == "10" + assert newsymbol1.is_constant is True # Test with multiple parameters of different types reader = FortranStringReader(''' @@ -94,14 +98,14 @@ def test_parameter_statements_work(): assert newsymbol3.is_constant assert newsymbol4.is_constant assert newsymbol5.is_constant - assert isinstance(newsymbol2.constant_value, Literal) - assert newsymbol2.constant_value.value == "1" - assert isinstance(newsymbol3.constant_value, Literal) - assert newsymbol3.constant_value.value == "3.14" - assert isinstance(newsymbol4.constant_value, Literal) - assert newsymbol4.constant_value.value == "true" - assert isinstance(newsymbol5.constant_value, Literal) - assert newsymbol5.constant_value.value == "a" + assert isinstance(newsymbol2.initial_value, Literal) + assert newsymbol2.initial_value.value == "1" + assert isinstance(newsymbol3.initial_value, Literal) + assert newsymbol3.initial_value.value == "3.14" + assert isinstance(newsymbol4.initial_value, Literal) + assert newsymbol4.initial_value.value == "true" + assert isinstance(newsymbol5.initial_value, Literal) + assert newsymbol5.initial_value.value == "a" @pytest.mark.usefixtures("f2008_parser") @@ -129,12 +133,12 @@ def test_parameter_statements_complex_case_work(): assert newsymbol1.is_constant assert newsymbol2.is_constant assert newsymbol3.is_constant - assert isinstance(newsymbol1.constant_value, Literal) - assert isinstance(newsymbol2.constant_value, Reference) - assert newsymbol2.constant_value.name == "var1" - assert isinstance(newsymbol3.constant_value, BinaryOperation) - assert newsymbol3.constant_value.children[0].name == "var1" - assert newsymbol3.constant_value.children[1].name == "var2" + assert isinstance(newsymbol1.initial_value, Literal) + assert isinstance(newsymbol2.initial_value, Reference) + assert newsymbol2.initial_value.name == "var1" + assert isinstance(newsymbol3.initial_value, BinaryOperation) + assert newsymbol3.initial_value.children[0].name == "var1" + assert newsymbol3.initial_value.children[1].name == "var2" @pytest.mark.usefixtures("f2008_parser") diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index 1562485987..9eb9ece999 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -699,11 +699,12 @@ def test_get_partial_datatype(): processor = Fparser2Reader() # Entry in symbol table with unmodified properties. - reader = FortranStringReader("integer :: l1") + reader = FortranStringReader("integer :: l1=2") node = Specification_Part(reader).content[0] ids = [id(entry) for entry in walk(node)] - datatype = processor._get_partial_datatype(node, fake_parent, {}) + datatype, init = processor._get_partial_datatype(node, fake_parent, {}) assert isinstance(datatype, ScalarType) + assert isinstance(init, Literal) assert datatype.intrinsic is ScalarType.Intrinsic.INTEGER # Check fparser2 tree is unmodified assert ids == [id(entry) for entry in walk(node)] @@ -713,8 +714,9 @@ def test_get_partial_datatype(): reader = FortranStringReader("integer, pointer :: l1 => null()") node = Specification_Part(reader).content[0] ids = [id(entry) for entry in walk(node)] - datatype = processor._get_partial_datatype(node, fake_parent, {}) + datatype, init = processor._get_partial_datatype(node, fake_parent, {}) assert isinstance(datatype, ScalarType) + assert isinstance(init, CodeBlock) assert datatype.intrinsic is ScalarType.Intrinsic.INTEGER # Check fparser2 tree is unmodified assert ids == [id(entry) for entry in walk(node)] @@ -724,8 +726,9 @@ def test_get_partial_datatype(): reader = FortranStringReader("real*4, target, dimension(10,20) :: l1") node = Specification_Part(reader).content[0] ids = [id(entry) for entry in walk(node)] - datatype = processor._get_partial_datatype(node, fake_parent, {}) + datatype, init = processor._get_partial_datatype(node, fake_parent, {}) assert isinstance(datatype, ArrayType) + assert init is None assert datatype.intrinsic is ScalarType.Intrinsic.REAL assert datatype.precision == 4 assert datatype.shape[0].upper.value == '10' @@ -739,7 +742,9 @@ def test_get_partial_datatype(): reader = FortranStringReader(" complex :: c\n") node = Specification_Part(reader).content[0] ids = [id(entry) for entry in walk(node)] - assert not processor._get_partial_datatype(node, fake_parent, {}) + dtype, init = processor._get_partial_datatype(node, fake_parent, {}) + assert dtype is None + assert init is None # Check fparser2 tree is unmodified assert ids == [id(entry) for entry in walk(node)] @@ -749,8 +754,9 @@ def test_get_partial_datatype(): "integer, pointer :: l1 => null(), l2 => null()") node = Specification_Part(reader).content[0] ids = [id(entry) for entry in walk(node)] - datatype = processor._get_partial_datatype(node, fake_parent, {}) + datatype, init = processor._get_partial_datatype(node, fake_parent, {}) assert isinstance(datatype, ScalarType) + assert isinstance(init, CodeBlock) assert datatype.intrinsic is ScalarType.Intrinsic.INTEGER # Check fparser2 tree is unmodified assert ids == [id(entry) for entry in walk(node)] @@ -817,43 +823,56 @@ def test_process_declarations(): processor.process_declarations(fake_parent, [fparser2spec], []) newsymbol = symtab.lookup("i1") assert newsymbol.is_constant - assert isinstance(newsymbol.constant_value, Literal) - assert newsymbol.constant_value.value == "1" + assert isinstance(newsymbol.initial_value, Literal) + assert newsymbol.initial_value.value == "1" reader = FortranStringReader("real, parameter :: i2 = 2.2, i3 = 3.3") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) - assert symtab.lookup("i2").constant_value.value == "2.2" - assert symtab.lookup("i3").constant_value.value == "3.3" + assert symtab.lookup("i2").initial_value.value == "2.2" + assert symtab.lookup("i3").initial_value.value == "3.3" # Initialisation with constant expressions reader = FortranStringReader("real, parameter :: i4 = 1.1, i5 = i4 * 2") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) - assert symtab.lookup("i4").constant_value.value == "1.1" - assert isinstance(symtab.lookup("i5").constant_value, BinaryOperation) + assert symtab.lookup("i4").initial_value.value == "1.1" + assert isinstance(symtab.lookup("i5").initial_value, BinaryOperation) # Initialisation with a constant expression (1) and with a symbol (val1) reader = FortranStringReader("integer, parameter :: val1 = 1, val2 = val1") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) - assert fake_parent.symbol_table.lookup("val1").constant_value.value == "1" + assert fake_parent.symbol_table.lookup("val1").initial_value.value == "1" assert isinstance( - fake_parent.symbol_table.lookup("val2").constant_value, Reference) - assert fake_parent.symbol_table.lookup("val2").constant_value.symbol == \ + fake_parent.symbol_table.lookup("val2").initial_value, Reference) + assert fake_parent.symbol_table.lookup("val2").initial_value.symbol == \ fake_parent.symbol_table.lookup("val1") # Initialisation with a complex constant expression - symtab.add(DataSymbol("precisionkind", INTEGER_TYPE, constant_value=4)) + symtab.add(DataSymbol("precisionkind", INTEGER_TYPE, is_constant=True, + initial_value=4)) reader = FortranStringReader( "integer, parameter :: val3 = 2 * (val1 + val2) + 2_precisionkind") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) - # Val3 has been given a constant expression - assert fake_parent.symbol_table.lookup("val3").constant_value + # Val3 has been given an initial_value expression + val3 = fake_parent.symbol_table.lookup("val3") + assert isinstance(val3.initial_value, BinaryOperation) + assert val3.is_constant # The new symbol (precisionkind) has been added to the parent Symbol Table assert fake_parent.symbol_table.lookup("precisionkind") + # Initialisation of a variable + reader = FortranStringReader( + "integer :: val4 = 2 * (val1 + val2) + 2_precisionkind") + fparser2spec = Specification_Part(reader).content[0] + processor.process_declarations(fake_parent, [fparser2spec], []) + val4 = fake_parent.symbol_table.lookup("val4") + assert val4.initial_value + assert isinstance(val4.initial_value, BinaryOperation) + assert val4.is_constant is False + # Check we catch duplicated symbols reader = FortranStringReader("integer :: i2") fparser2spec = Specification_Part(reader).content[0] @@ -862,6 +881,16 @@ def test_process_declarations(): assert ("Symbol 'i2' already present in SymbolTable with a defined " "interface" in str(error.value)) + # Initialisation of a pointer. + reader = FortranStringReader( + "real, dimension(:), pointer :: dptr => null()") + fparser2spec = Specification_Part(reader).content[0] + processor.process_declarations(fake_parent, [fparser2spec], []) + ptr_sym = fake_parent.symbol_table.lookup("dptr") + assert isinstance(ptr_sym, DataSymbol) + assert isinstance(ptr_sym.datatype, UnknownFortranType) + assert isinstance(ptr_sym.initial_value, CodeBlock) + @pytest.mark.usefixtures("f2008_parser") def test_process_declarations_unknownfortrantype(): @@ -939,15 +968,13 @@ def test_process_declarations_errors(): @pytest.mark.usefixtures("f2008_parser") def test_declarations_with_initialisations(fortran_reader): '''Test that Fparser2Reader keeps all the variable initialisation - expressions, even though some may end up in UnknownTypes for now - because we don't support variable initialisations that are not - parameters. + expressions. ''' psyir = fortran_reader.psyir_from_source( """ module test - integer :: a = 1 + integer :: a = 1, aa = 4 integer, save :: b = 1 integer, parameter :: c = 1 contains @@ -960,32 +987,33 @@ def test_declarations_with_initialisations(fortran_reader): """) inner_st = psyir.walk(Routine)[0].symbol_table + asym = inner_st.lookup('a') + aasym = inner_st.lookup('aa') + bsym = inner_st.lookup('b') + csym = inner_st.lookup('c') + dsym = inner_st.lookup('d') + esym = inner_st.lookup('e') + fsym = inner_st.lookup('f') + all_syms = [asym, aasym, bsym, csym, dsym, esym, fsym] + # All initialisation variables are DataSymbols - assert isinstance(inner_st.lookup('a'), DataSymbol) - assert isinstance(inner_st.lookup('b'), DataSymbol) - assert isinstance(inner_st.lookup('c'), DataSymbol) - assert isinstance(inner_st.lookup('d'), DataSymbol) - assert isinstance(inner_st.lookup('e'), DataSymbol) - assert isinstance(inner_st.lookup('f'), DataSymbol) - - # When it is not a parameter they are unknown interface and datatype - assert isinstance(inner_st.lookup('a').interface, UnknownInterface) - assert isinstance(inner_st.lookup('b').interface, UnknownInterface) - assert isinstance(inner_st.lookup('d').interface, UnknownInterface) - assert isinstance(inner_st.lookup('e').interface, UnknownInterface) - assert isinstance(inner_st.lookup('a').datatype, UnknownFortranType) - assert isinstance(inner_st.lookup('b').datatype, UnknownFortranType) - assert isinstance(inner_st.lookup('d').datatype, UnknownFortranType) - assert isinstance(inner_st.lookup('e').datatype, UnknownFortranType) - - # When it is a parameter the interface, type and constant_value is defined - assert isinstance(inner_st.lookup('c').interface, DefaultModuleInterface) - assert isinstance(inner_st.lookup('c').datatype, ScalarType) - assert isinstance(inner_st.lookup('c').constant_value, Literal) - assert isinstance(inner_st.lookup('f').interface, AutomaticInterface) - assert isinstance(inner_st.lookup('f').datatype, ScalarType) - assert isinstance(inner_st.lookup('f').constant_value, Literal) - assert isinstance(inner_st.lookup('f').interface, AutomaticInterface) + assert all(isinstance(sym, DataSymbol) for sym in all_syms) + + # All of the data symbols should have a StaticInterface (because they + # either have an explicit 'save' or 'parameter' or are given an + # initial_value with then implies 'save'). + assert all(isinstance(sym.interface, StaticInterface) for sym in all_syms) + + # All symbols should have a known data type. + assert all(isinstance(sym.datatype, ScalarType) for sym in all_syms) + + # When it is a parameter the initial_value is defined and is_constant + # is True. + assert isinstance(csym.initial_value, Literal) + assert csym.is_constant is True + + assert isinstance(fsym.initial_value, Literal) + assert fsym.is_constant is True @pytest.mark.usefixtures("f2008_parser") @@ -1043,49 +1071,30 @@ def test_process_unsupported_declarations(fortran_reader): fake_parent = KernelSchedule("dummy_schedule") processor = Fparser2Reader() - # Initial values for variables are not supported so we should get a symbol - # with unknown type. - reader = FortranStringReader("real:: a = 1.1") - fparser2spec = Specification_Part(reader).content[0] - processor.process_declarations(fake_parent, [fparser2spec], []) - asym = fake_parent.symbol_table.lookup("a") - assert isinstance(asym.datatype, UnknownFortranType) - assert asym.datatype.declaration == "REAL :: a = 1.1" - - reader = FortranStringReader("real:: b = 1.1, c = 2.2") - fparser2spec = Specification_Part(reader).content[0] - processor.process_declarations(fake_parent, [fparser2spec], []) - bsym = fake_parent.symbol_table.lookup("b") - assert isinstance(bsym.datatype, UnknownFortranType) - assert bsym.datatype.declaration == "REAL :: b = 1.1" - csym = fake_parent.symbol_table.lookup("c") - assert isinstance(csym.datatype, UnknownFortranType) - assert csym.datatype.declaration == "REAL :: c = 2.2" - # Multiple symbols with a single attribute - reader = FortranStringReader("integer, private :: d = 1, e = 2") + reader = FortranStringReader("integer, private, pointer :: d, e") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) dsym = fake_parent.symbol_table.lookup("d") assert isinstance(dsym.datatype, UnknownFortranType) - assert dsym.datatype.declaration == "INTEGER, PRIVATE :: d = 1" + assert dsym.datatype.declaration == "INTEGER, PRIVATE, POINTER :: d" esym = fake_parent.symbol_table.lookup("e") assert isinstance(esym.datatype, UnknownFortranType) - assert esym.datatype.declaration == "INTEGER, PRIVATE :: e = 2" + assert esym.datatype.declaration == "INTEGER, PRIVATE, POINTER :: e" # Multiple attributes reader = FortranStringReader( - "INTEGER, PRIVATE, DIMENSION(3) :: f = 2, g = 3") + "INTEGER, PRIVATE, DIMENSION(3), POINTER :: f, g") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) fsym = fake_parent.symbol_table.lookup("f") assert isinstance(fsym.datatype, UnknownFortranType) assert (fsym.datatype.declaration == - "INTEGER, PRIVATE, DIMENSION(3) :: f = 2") + "INTEGER, PRIVATE, DIMENSION(3), POINTER :: f") gsym = fake_parent.symbol_table.lookup("g") assert isinstance(gsym.datatype, UnknownFortranType) assert (gsym.datatype.declaration == - "INTEGER, PRIVATE, DIMENSION(3) :: g = 3") + "INTEGER, PRIVATE, DIMENSION(3), POINTER :: g") # Test with unsupported intrinsic type. Note the space before complex # below which stops the line being treated as a comment. @@ -1118,16 +1127,16 @@ def test_process_unsupported_declarations(fortran_reader): processor.process_declarations(fake_parent, [fparser2spec], []) fbsym = fake_parent.symbol_table.lookup("fbsp") assert fbsym.datatype.intrinsic == ScalarType.Intrinsic.INTEGER - assert isinstance(fbsym.constant_value, CodeBlock) + assert isinstance(fbsym.initial_value, CodeBlock) # The first parameter should have been handled correctly hsym = fake_parent.symbol_table.lookup("happy") assert hsym.datatype.intrinsic == ScalarType.Intrinsic.INTEGER - assert hsym.constant_value.value == "1" + assert hsym.initial_value.value == "1" # As should the third ssym = fake_parent.symbol_table.lookup("sad") assert ssym.datatype.intrinsic == ScalarType.Intrinsic.INTEGER - assert isinstance(ssym.constant_value, Reference) - assert ssym.constant_value.symbol.name == "fbsp" + assert isinstance(ssym.initial_value, Reference) + assert ssym.initial_value.symbol.name == "fbsp" @pytest.mark.usefixtures("f2008_parser") @@ -1144,16 +1153,16 @@ def test_unsupported_decln_initial_value(monkeypatch): # for anything other than a Literal. class BrokenDataSymbol(DataSymbol): - ''' Sub-class of DataSymbol with `constant_value` setter patched + ''' Sub-class of DataSymbol with `initial_value` setter patched so that it raises a ValueError for anything other than a Literal. ''' @property - def constant_value(self): - return self._constant_value + def initial_value(self): + return self._initial_value - @constant_value.setter - def constant_value(self, value): + @initial_value.setter + def initial_value(self, value): if isinstance(value, Literal): - self._constant_value = value + self._initial_value = value else: raise ValueError("") @@ -1168,7 +1177,7 @@ def constant_value(self, value): processor.process_declarations(fake_parent, [fparser2spec], []) hsym = fake_parent.symbol_table.lookup("happy") assert hsym.datatype.intrinsic == ScalarType.Intrinsic.INTEGER - assert hsym.constant_value.value == "1" + assert hsym.initial_value.value == "1" fbsym = fake_parent.symbol_table.lookup("fbsp") assert isinstance(fbsym.datatype, UnknownFortranType) assert (fbsym.datatype.declaration == "INTEGER, PRIVATE, PARAMETER :: " diff --git a/src/psyclone/tests/psyir/nodes/array_mixin_test.py b/src/psyclone/tests/psyir/nodes/array_mixin_test.py index 045506ca2e..b7dc4d6ef4 100644 --- a/src/psyclone/tests/psyir/nodes/array_mixin_test.py +++ b/src/psyclone/tests/psyir/nodes/array_mixin_test.py @@ -290,8 +290,8 @@ def test_get_lbound_expression(): ''' # Symbol is of ArrayType. - lbound = DataSymbol("jmin", INTEGER_TYPE, - constant_value=Literal("3", INTEGER_TYPE)) + lbound = DataSymbol("jmin", INTEGER_TYPE, is_constant=True, + initial_value=Literal("3", INTEGER_TYPE)) lbnd_ref = Reference(lbound) symbol = DataSymbol("my_symbol", ArrayType(INTEGER_TYPE, [10, (2, 10), (lbnd_ref, 10)])) diff --git a/src/psyclone/tests/psyir/symbols/datasymbol_test.py b/src/psyclone/tests/psyir/symbols/datasymbol_test.py index c6612fd9ce..e2819e5429 100644 --- a/src/psyclone/tests/psyir/symbols/datasymbol_test.py +++ b/src/psyclone/tests/psyir/symbols/datasymbol_test.py @@ -43,12 +43,12 @@ from fparser.common.readfortran import FortranStringReader from fparser.two import Fortran2003 -from psyclone.psyir.symbols import DataSymbol, ContainerSymbol, \ - AutomaticInterface, ImportInterface, ArgumentInterface, \ - ScalarType, ArrayType, REAL_SINGLE_TYPE, REAL_DOUBLE_TYPE, REAL4_TYPE, \ - REAL8_TYPE, INTEGER_SINGLE_TYPE, INTEGER_DOUBLE_TYPE, INTEGER4_TYPE, \ - BOOLEAN_TYPE, CHARACTER_TYPE, DeferredType, Symbol, DataTypeSymbol, \ - UnresolvedInterface +from psyclone.psyir.symbols import ( + DataSymbol, ContainerSymbol, Symbol, DataTypeSymbol, AutomaticInterface, + ImportInterface, ArgumentInterface, UnresolvedInterface, + ScalarType, ArrayType, REAL_SINGLE_TYPE, REAL_DOUBLE_TYPE, REAL4_TYPE, + REAL8_TYPE, INTEGER_SINGLE_TYPE, INTEGER_DOUBLE_TYPE, INTEGER4_TYPE, + BOOLEAN_TYPE, CHARACTER_TYPE, DeferredType) from psyclone.psyir.nodes import (Literal, Reference, BinaryOperation, Return, CodeBlock) @@ -65,19 +65,31 @@ def test_datasymbol_initialisation(): real_kind_type = ScalarType(ScalarType.Intrinsic.REAL, kind) assert isinstance(DataSymbol('a', real_kind_type), DataSymbol) - # real constants are not currently supported assert isinstance(DataSymbol('a', INTEGER_SINGLE_TYPE), DataSymbol) - assert isinstance(DataSymbol('a', INTEGER_DOUBLE_TYPE, constant_value=0), - DataSymbol) + # Run-time constant. + sym = DataSymbol('a', REAL_DOUBLE_TYPE, is_constant=True, + initial_value=0.0) + assert isinstance(sym, DataSymbol) + assert sym.is_constant + # Defaults to StaticInterface for a run-time constant. + assert sym.is_static + # Run-time constant can only have StaticInterface or ImportInterface. + with pytest.raises(ValueError) as err: + _ = DataSymbol('a', INTEGER_DOUBLE_TYPE, is_constant=True, + initial_value=1, interface=AutomaticInterface()) + assert ("A DataSymbol representing a constant must have either a " + "StaticInterface or an ImportInterface but 'a' has interface " + "'Automatic'" in str(err.value)) + assert isinstance(DataSymbol('a', INTEGER4_TYPE), DataSymbol) assert isinstance(DataSymbol('a', CHARACTER_TYPE), DataSymbol) - assert isinstance(DataSymbol('a', CHARACTER_TYPE, - constant_value="hello"), DataSymbol) + assert isinstance(DataSymbol('a', CHARACTER_TYPE, is_constant=True, + initial_value="hello"), DataSymbol) assert isinstance(DataSymbol('a', BOOLEAN_TYPE), DataSymbol) - assert isinstance(DataSymbol('a', BOOLEAN_TYPE, - constant_value=False), + assert isinstance(DataSymbol('a', BOOLEAN_TYPE, is_constant=True, + initial_value=False), DataSymbol) array_type = ArrayType(REAL_SINGLE_TYPE, [ArrayType.Extent.ATTRIBUTE]) assert isinstance(DataSymbol('a', array_type), DataSymbol) @@ -126,25 +138,44 @@ def test_datasymbol_specialise_and_process_arguments(): sym2 = Symbol("symbol2") sym2.specialise(DataSymbol, datatype=REAL_SINGLE_TYPE) assert sym2.datatype is REAL_SINGLE_TYPE - assert sym2.constant_value is None + assert sym2.is_constant is False + assert sym2.initial_value is None - # Include a constant_value + # Include an initial_value sym3 = Symbol("symbol3") sym3.specialise(DataSymbol, datatype=REAL_SINGLE_TYPE, - constant_value=3.14) + initial_value=3.14) assert sym3.datatype is REAL_SINGLE_TYPE - assert isinstance(sym3.constant_value, Literal) - assert sym3.constant_value.value == '3.14' + assert isinstance(sym3.initial_value, Literal) + assert sym3.initial_value.value == '3.14' - # Include a constant_value of the wrong type + # Include an initial_value of the wrong type sym4 = Symbol("symbol4") with pytest.raises(ValueError) as error: sym4.specialise(DataSymbol, datatype=INTEGER_SINGLE_TYPE, - constant_value=3.14) + initial_value=3.14) assert ("This DataSymbol instance datatype is 'Scalar' " - "meaning the constant value should be" + "meaning the initial value should be" in str(error.value)) + # Attempt to specify that the symbol is constant but without providing an + # initial value. + sym5 = Symbol("symbol5") + with pytest.raises(ValueError) as error: + sym5.specialise(DataSymbol, datatype=INTEGER_SINGLE_TYPE, + is_constant=True) + assert ("DataSymbol 'symbol5' does not have an initial value set and is " + "not imported and therefore cannot be a constant." + in str(error.value)) + # The absence of an initial value is permitted if the symbol has an + # ImportInterface. + csym = ContainerSymbol("some_mod") + sym6 = Symbol("symbol6") + sym6.specialise(DataSymbol, datatype=INTEGER_SINGLE_TYPE, + is_constant=True, interface=ImportInterface(csym)) + assert sym6.is_constant + assert sym6.is_import + def test_datasymbol_can_be_printed(): '''Test that a DataSymbol instance can always be printed. (i.e. is @@ -170,115 +201,135 @@ def test_datasymbol_can_be_printed(): assert ("s3: DataSymbol, Import(container='my_mod')>" in str(sym3)) - sym3 = DataSymbol("s3", INTEGER_SINGLE_TYPE, constant_value=12) + sym3 = DataSymbol("s3", INTEGER_SINGLE_TYPE, initial_value=12) assert ("s3: DataSymbol, Automatic, " - "constant_value=Literal" + "initial_value=Literal" "[value:'12', Scalar]>" in str(sym3)) - + sym3.is_constant = True + assert ("s3: DataSymbol, Automatic, " + "initial_value=Literal" + "[value:'12', Scalar], constant=True>" + in str(sym3)) sym4 = DataSymbol("s4", INTEGER_SINGLE_TYPE, interface=UnresolvedInterface()) assert "s4: DataSymbol, Unresolved>" in str(sym4) -def test_datasymbol_constant_value_setter(): - '''Test that a DataSymbol constant value can be set if given a new valid - constant value.''' +def test_datasymbol_initial_value_setter(): + '''Test that a DataSymbol initial value can be set if given a new valid + value.''' # Test with valid constant values - sym = DataSymbol('a', INTEGER_SINGLE_TYPE, constant_value=7) - assert sym.constant_value.value == "7" - sym.constant_value = 9 - assert sym.constant_value.value == "9" + sym = DataSymbol('a', INTEGER_SINGLE_TYPE, initial_value=7) + assert sym.initial_value.value == "7" + sym.initial_value = 9 + assert sym.initial_value.value == "9" - sym = DataSymbol('a', REAL_SINGLE_TYPE, constant_value=3.1415) - assert sym.constant_value.value == "3.1415" - sym.constant_value = 1.0 - assert sym.constant_value.value == "1.0" + sym = DataSymbol('a', REAL_SINGLE_TYPE, initial_value=3.1415) + assert sym.initial_value.value == "3.1415" + sym.initial_value = 1.0 + assert sym.initial_value.value == "1.0" - sym = DataSymbol('a', BOOLEAN_TYPE, constant_value=True) - assert sym.constant_value.value == "true" - sym.constant_value = False - assert sym.constant_value.value == "false" + sym = DataSymbol('a', BOOLEAN_TYPE, initial_value=True) + assert sym.initial_value.value == "true" + sym.initial_value = False + assert sym.initial_value.value == "false" # Test with valid constant expressions lhs = Literal('2', INTEGER_SINGLE_TYPE) rhs = Reference(DataSymbol('constval', INTEGER_SINGLE_TYPE)) ct_expr = BinaryOperation.create(BinaryOperation.Operator.ADD, lhs, rhs) - sym = DataSymbol('a', INTEGER_SINGLE_TYPE, constant_value=ct_expr) - assert isinstance(sym.constant_value, BinaryOperation) - assert sym.constant_value is ct_expr + sym = DataSymbol('a', INTEGER_SINGLE_TYPE, initial_value=ct_expr) + assert isinstance(sym.initial_value, BinaryOperation) + assert sym.initial_value is ct_expr - # Test setting it back to non-constant - sym.constant_value = None - assert sym.constant_value is None + # Test setting it back to nothing + sym.initial_value = None + assert sym.initial_value is None -def test_datasymbol_constant_value_setter_invalid(): - '''Test that a DataSymbol constant value setter raises the appropriate +def test_datasymbol_initial_value_setter_invalid(): + '''Test that the DataSymbol initial_value setter raises the appropriate error if an invalid value and/or datatype are given.''' # Test with invalid constant values sym = DataSymbol('a', DeferredType()) with pytest.raises(ValueError) as error: - sym.constant_value = 1.0 - assert ("Error setting constant value for symbol 'a'. A DataSymbol with " - "a constant value must be a scalar or an array but found " - "'DeferredType'." in str(error.value)) + sym.initial_value = 1.0 + assert ("Error setting initial value for symbol 'a'. A DataSymbol with " + "an initial value must be a scalar or an array or of UnknownType " + "but found 'DeferredType'." in str(error.value)) - # Test with invalid constant expressions + # Test with invalid initial expressions ct_expr = Return() with pytest.raises(ValueError) as error: - _ = DataSymbol('a', INTEGER_SINGLE_TYPE, constant_value=ct_expr) - assert ("Error setting constant value for symbol 'a'. PSyIR static " + _ = DataSymbol('a', INTEGER_SINGLE_TYPE, initial_value=ct_expr) + assert ("Error setting initial value for symbol 'a'. PSyIR static " "expressions can only contain PSyIR Literal, Operation, Reference " "or CodeBlock nodes but found:" in str(error.value)) with pytest.raises(ValueError) as error: DataSymbol('a', INTEGER_SINGLE_TYPE, interface=ArgumentInterface(), - constant_value=9) - assert ("Error setting constant value for symbol 'a'. A DataSymbol with " - "an ArgumentInterface can not have a constant value." + initial_value=9) + assert ("Error setting initial value for symbol 'a'. A DataSymbol with " + "an ArgumentInterface can not have an initial value." in str(error.value)) with pytest.raises(ValueError) as error: - DataSymbol('a', INTEGER_SINGLE_TYPE, constant_value=9.81) - assert ("Error setting constant value for symbol 'a'. This DataSymbol " + DataSymbol('a', INTEGER_SINGLE_TYPE, initial_value=9.81) + assert ("Error setting initial value for symbol 'a'. This DataSymbol " "instance datatype is 'Scalar' meaning " - "the constant value should be") in str(error.value) + "the initial value should be") in str(error.value) assert "'int'>' but found " in str(error.value) assert "'float'>'." in str(error.value) with pytest.raises(ValueError) as error: - DataSymbol('a', CHARACTER_TYPE, constant_value=42) - assert ("Error setting constant value for symbol 'a'. This DataSymbol " + DataSymbol('a', CHARACTER_TYPE, initial_value=42) + assert ("Error setting initial value for symbol 'a'. This DataSymbol " "instance datatype is 'Scalar' meaning " - "the constant value should be") in str(error.value) + "the initial value should be") in str(error.value) assert "'str'>' but found " in str(error.value) assert "'int'>'." in str(error.value) with pytest.raises(ValueError) as error: - DataSymbol('a', BOOLEAN_TYPE, constant_value="hello") - assert ("Error setting constant value for symbol 'a'. This DataSymbol " + DataSymbol('a', BOOLEAN_TYPE, initial_value="hello") + assert ("Error setting initial value for symbol 'a'. This DataSymbol " "instance datatype is 'Scalar' meaning " - "the constant value should be") in str(error.value) + "the initial value should be") in str(error.value) assert "'bool'>' but found " in str(error.value) assert "'str'>'." in str(error.value) + # is_constant specified but without an initial_value + with pytest.raises(ValueError) as error: + DataSymbol('a', BOOLEAN_TYPE, is_constant=True) + assert ("DataSymbol 'a' does not have an initial value set and is not " + "imported and therefore cannot be a constant" in str(error.value)) + def test_datasymbol_is_constant(): '''Test that the DataSymbol is_constant property returns True if a constant value is set and False if it is not. ''' - sym = DataSymbol('a', INTEGER_SINGLE_TYPE) + sym = DataSymbol('a', INTEGER_SINGLE_TYPE, initial_value=9) assert not sym.is_constant - sym.constant_value = 9 + sym.is_constant = True assert sym.is_constant + with pytest.raises(ValueError) as err: + sym.initial_value = None + assert ("DataSymbol 'a' is a constant and not imported and therefore " + "must have an initial value but got None" in str(err.value)) + sym.is_constant = False + sym.initial_value = None + with pytest.raises(ValueError) as err: + sym.is_constant = True + assert ("DataSymbol 'a' does not have an initial value set and is not " + "imported and therefore cannot be a constant." in str(err.value)) @pytest.mark.usefixtures("parser") -def test_datasymbol_is_constant_codeblock(): - ''' Test that a DataSymbol can have a CodeBlock as its constant value. ''' +def test_datasymbol_initial_value_codeblock(): + ''' Test that a DataSymbol can have a CodeBlock as its initial value. ''' sym = DataSymbol('a', INTEGER_SINGLE_TYPE) reader = FortranStringReader( "INTEGER, PARAMETER :: a=SELECTED_REAL_KIND(6,37)") @@ -287,9 +338,9 @@ def test_datasymbol_is_constant_codeblock(): # the basis for our CodeBlock inits = Fortran2003.walk(fparser2spec, Fortran2003.Initialization) cblock = CodeBlock([inits[0].children[1]], CodeBlock.Structure.EXPRESSION) - assert not sym.is_constant - sym.constant_value = cblock - assert sym.is_constant + assert sym.initial_value is None + sym.initial_value = cblock + assert isinstance(sym.initial_value, CodeBlock) def test_datasymbol_scalar_array(): @@ -314,7 +365,7 @@ def test_datasymbol_copy(): ''' array_type = ArrayType(REAL_SINGLE_TYPE, [1, 2]) - symbol = DataSymbol("myname", array_type, constant_value=None, + symbol = DataSymbol("myname", array_type, initial_value=None, interface=ArgumentInterface( ArgumentInterface.Access.READWRITE)) new_symbol = symbol.copy() @@ -323,11 +374,12 @@ def test_datasymbol_copy(): assert symbol.name == new_symbol.name assert symbol.datatype == new_symbol.datatype assert symbol.shape == new_symbol.shape - assert symbol.constant_value == new_symbol.constant_value + assert symbol.initial_value == new_symbol.initial_value + assert symbol.is_constant == new_symbol.is_constant assert symbol.interface == new_symbol.interface # Change the properties of the new symbol and check the original - # is not affected. Can't check constant_value yet as we have a + # is not affected. Can't check initial_value yet as we have a # shape value new_symbol._name = "new" new_symbol.datatype = ArrayType(ScalarType(ScalarType.Intrinsic.INTEGER, @@ -357,10 +409,10 @@ def test_datasymbol_copy(): ScalarType.Intrinsic.INTEGER) assert (symbol.shape[1].upper.datatype.precision == ScalarType.Precision.UNDEFINED) - assert not symbol.constant_value + assert symbol.initial_value is None - # Now check constant_value - new_symbol.constant_value = 3 + # Now check initial_value + new_symbol.initial_value = 3 assert isinstance(symbol.shape[0].upper, Literal) assert symbol.shape[0].upper.value == "1" @@ -374,13 +426,13 @@ def test_datasymbol_copy(): ScalarType.Intrinsic.INTEGER) assert (symbol.shape[1].upper.datatype.precision == ScalarType.Precision.UNDEFINED) - assert not symbol.constant_value + assert symbol.initial_value is None def test_datasymbol_copy_properties(): '''Test that the DataSymbol copy_properties method works as expected.''' array_type = ArrayType(REAL_SINGLE_TYPE, [1, 2]) - symbol = DataSymbol("myname", array_type, constant_value=None, + symbol = DataSymbol("myname", array_type, initial_value=None, interface=ArgumentInterface( ArgumentInterface.Access.READWRITE)) @@ -391,7 +443,7 @@ def test_datasymbol_copy_properties(): "") in str(excinfo.value) new_symbol = DataSymbol("other_name", INTEGER_SINGLE_TYPE, - constant_value=7) + initial_value=7) symbol.copy_properties(new_symbol) @@ -399,11 +451,11 @@ def test_datasymbol_copy_properties(): assert symbol.datatype.intrinsic == ScalarType.Intrinsic.INTEGER assert symbol.datatype.precision == ScalarType.Precision.SINGLE assert symbol.is_automatic - assert isinstance(symbol.constant_value, Literal) - assert symbol.constant_value.value == "7" - assert (symbol.constant_value.datatype.intrinsic == + assert isinstance(symbol.initial_value, Literal) + assert symbol.initial_value.value == "7" + assert (symbol.initial_value.datatype.intrinsic == symbol.datatype.intrinsic) - assert (symbol.constant_value.datatype.precision == + assert (symbol.initial_value.datatype.precision == symbol.datatype.precision) @@ -437,7 +489,7 @@ def test_datasymbol_shape(): def test_datasymbol_str(): '''Test that the DataSymbol __str__ method returns the expected string''' - data_symbol = DataSymbol("a", INTEGER4_TYPE, constant_value=3) + data_symbol = DataSymbol("a", INTEGER4_TYPE, initial_value=3) assert (data_symbol.__str__() == - "a: DataSymbol, Automatic, constant_value=" + "a: DataSymbol, Automatic, initial_value=" "Literal[value:'3', Scalar]>") diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index bb6cdf15b0..993f3215a8 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -157,7 +157,8 @@ def test_scalartype_datasymbol_precision(intrinsic): # Create an r_def precision symbol with a constant value of 8 data_type = ScalarType(ScalarType.Intrinsic.INTEGER, ScalarType.Precision.UNDEFINED) - precision_symbol = DataSymbol("r_def", data_type, constant_value=8) + precision_symbol = DataSymbol("r_def", data_type, is_constant=True, + initial_value=8) # Set the precision of our ScalarType to be the precision symbol scalar_type = ScalarType(intrinsic, precision_symbol) assert isinstance(scalar_type, ScalarType) @@ -177,7 +178,8 @@ def test_scalartype_not_equal(): intrinsic = ScalarType.Intrinsic.INTEGER data_type = ScalarType(ScalarType.Intrinsic.INTEGER, ScalarType.Precision.UNDEFINED) - precision_symbol = DataSymbol("r_def", data_type, constant_value=8) + precision_symbol = DataSymbol("r_def", data_type, is_constant=True, + initial_value=8) # Set the precision of our ScalarType to be the precision symbol scalar_type = ScalarType(intrinsic, precision_symbol) # Same precision symbol but different intrinsic type @@ -268,7 +270,8 @@ def test_arraytype(): '''Test that the ArrayType class __init__ works as expected. Test the different dimension datatypes that are supported.''' scalar_type = ScalarType(ScalarType.Intrinsic.INTEGER, 4) - data_symbol = DataSymbol("var", scalar_type, constant_value=30) + data_symbol = DataSymbol("var", scalar_type, is_constant=True, + initial_value=30) one = Literal("1", scalar_type) var_plus_1 = BinaryOperation.create( BinaryOperation.Operator.ADD, Reference(data_symbol), one) @@ -382,7 +385,8 @@ def test_arraytype_invalid_shape_dimension_1(): ''' scalar_type = ScalarType(ScalarType.Intrinsic.REAL, 4) - symbol = DataSymbol("fred", scalar_type, constant_value=3.0) + symbol = DataSymbol("fred", scalar_type, is_constant=True, + initial_value=3.0) with pytest.raises(TypeError) as excinfo: _ = ArrayType(scalar_type, [Reference(symbol)]) assert ( @@ -457,7 +461,7 @@ def test_arraytype_invalid_shape_bounds(): assert ("If present, the lower bound in an ArrayType 'shape' must " "represent a value but found ArrayType.Extent" in str(err.value)) scalar_type = ScalarType(ScalarType.Intrinsic.REAL, 4) - symbol = DataSymbol("fred", scalar_type, constant_value=3.0) + symbol = DataSymbol("fred", scalar_type, initial_value=3.0) with pytest.raises(TypeError) as excinfo: _ = ArrayType(scalar_type, [(1, Reference(symbol))]) assert ( @@ -503,7 +507,8 @@ def test_arraytype_str(): '''Test that the ArrayType class str method works as expected.''' scalar_type = ScalarType(ScalarType.Intrinsic.INTEGER, ScalarType.Precision.UNDEFINED) - data_symbol = DataSymbol("var", scalar_type, constant_value=20) + data_symbol = DataSymbol("var", scalar_type, is_constant=True, + initial_value=20) data_type = ArrayType(scalar_type, [10, Reference(data_symbol), (2, Reference(data_symbol)), (Reference(data_symbol), 10)]) diff --git a/src/psyclone/tests/psyir/symbols/symbol_table_test.py b/src/psyclone/tests/psyir/symbols/symbol_table_test.py index 5246b4825c..f93298c255 100644 --- a/src/psyclone/tests/psyir/symbols/symbol_table_test.py +++ b/src/psyclone/tests/psyir/symbols/symbol_table_test.py @@ -544,7 +544,7 @@ def test_swap_symbol(): assert ("Symbol to remove must be of type Symbol but got 'str'" in str(err.value)) # Test that we reject attempts to swap symbols with different names. - symbol2 = DataSymbol("var2", INTEGER_TYPE, constant_value=6) + symbol2 = DataSymbol("var2", INTEGER_TYPE, initial_value=6) with pytest.raises(SymbolError) as err: sym_table.swap(symbol1, symbol2) assert ("Cannot swap symbols that have different names, got: 'var1' and " @@ -651,7 +651,7 @@ def test_table_merge(): # 'Own' routine symbol excluded. table2.add(RoutineSymbol("dent"), tag="own_routine_symbol") # Precision symbol should be included. - wp_sym = DataSymbol("wp", INTEGER_TYPE, constant_value=8) + wp_sym = DataSymbol("wp", INTEGER_TYPE, is_constant=True, initial_value=8) table2.add(wp_sym) table2.add(DataSymbol("marvin", ScalarType(ScalarType.Intrinsic.REAL, wp_sym))) @@ -804,7 +804,8 @@ def test_swap_symbol_properties(): ''' Test the symboltable swap_properties method ''' # pylint: disable=too-many-statements - symbol1 = DataSymbol("var1", INTEGER_TYPE, constant_value=7) + symbol1 = DataSymbol("var1", INTEGER_TYPE, is_constant=True, + initial_value=7) symbol2 = DataSymbol("dim1", INTEGER_TYPE, interface=ArgumentInterface( ArgumentInterface.Access.READ)) @@ -863,18 +864,18 @@ def test_swap_symbol_properties(): assert symbol1.datatype.shape[0].upper.symbol == symbol2 assert symbol1.datatype.shape[1].upper.symbol == symbol3 assert symbol1.is_argument - assert symbol1.constant_value is None + assert symbol1.initial_value is None assert symbol1.interface.access == ArgumentInterface.Access.READWRITE assert symbol4.name == "var2" assert symbol4.datatype.intrinsic == ScalarType.Intrinsic.INTEGER assert symbol4.datatype.precision == ScalarType.Precision.UNDEFINED assert not symbol4.shape - assert symbol4.is_automatic - assert symbol4.constant_value.value == "7" - assert (symbol4.constant_value.datatype.intrinsic == + assert symbol4.is_static + assert symbol4.initial_value.value == "7" + assert (symbol4.initial_value.datatype.intrinsic == symbol4.datatype.intrinsic) - assert (symbol4.constant_value.datatype.precision == + assert (symbol4.initial_value.datatype.precision == symbol4.datatype.precision) # Check symbol references are unaffected @@ -1743,7 +1744,7 @@ def test_new_symbol(): assert isinstance(sym2.interface, AutomaticInterface) assert isinstance(sym1.datatype, NoType) assert sym2.datatype is INTEGER_TYPE - assert sym2.constant_value is None + assert sym2.initial_value is None # The initialization parameters of new symbols can be given as # keyword parameters @@ -1754,7 +1755,8 @@ def test_new_symbol(): sym2 = symtab.new_symbol("data", symbol_type=DataSymbol, datatype=INTEGER_TYPE, visibility=Symbol.Visibility.PRIVATE, - constant_value=3) + is_constant=True, + initial_value=3) assert sym1.name == "routine_1" assert sym2.name == "data_1" assert type(sym1) == RoutineSymbol @@ -1765,7 +1767,8 @@ def test_new_symbol(): assert sym2.visibility is Symbol.Visibility.PRIVATE assert isinstance(sym1.datatype, DeferredType) assert sym2.datatype is INTEGER_TYPE - assert sym2.constant_value is not None + assert sym2.initial_value is not None + assert sym2.is_constant is True # Check that symbol_type only accepts symbols with pytest.raises(TypeError) as err: @@ -1815,12 +1818,14 @@ def test_find_or_create(): symbol_type=DataSymbol, datatype=INTEGER_TYPE, visibility=Symbol.Visibility.PRIVATE, - constant_value=3) + is_constant=True, + initial_value=3) assert new2.name == "new2" assert isinstance(new2, DataSymbol) assert new2.datatype is INTEGER_TYPE assert new2.visibility is Symbol.Visibility.PRIVATE - assert new2.constant_value.value == "3" + assert new2.initial_value.value == "3" + assert new2.is_constant is True assert symtab.lookup_with_tag("mytag") is new2 # Check that it fails if the named Symbol exists but is not of the @@ -1861,12 +1866,14 @@ def test_find_or_create_tag(): symbol_type=DataSymbol, datatype=INTEGER_TYPE, visibility=Symbol.Visibility.PRIVATE, - constant_value=3) + is_constant=True, + initial_value=3) assert symtab.lookup_with_tag("tag3") is tag3 assert type(tag3) == DataSymbol assert tag3.visibility is Symbol.Visibility.PRIVATE assert tag3.datatype is INTEGER_TYPE - assert tag3.constant_value is not None + assert tag3.is_constant is True + assert tag3.initial_value is not None # It can be given a different root_name tag4 = symtab.find_or_create_tag("tag4", root_name="var") @@ -2179,7 +2186,7 @@ def test_resolve_imports(fortran_reader, tmpdir, monkeypatch): # b_1 have all relevant info now assert isinstance(b_1, DataSymbol) assert b_1.datatype.intrinsic.name == 'INTEGER' - assert b_1.constant_value.value == "10" + assert b_1.initial_value.value == "10" # The interface is also updated updated now because we know where it comes # from assert isinstance(b_1.interface, ImportInterface) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py index 6747d23541..96ccca5b89 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py @@ -56,7 +56,7 @@ def create_matmul(): symbol_table = SymbolTable() one = Literal("1", INTEGER_TYPE) two = Literal("2", INTEGER_TYPE) - index = DataSymbol("idx", INTEGER_TYPE, constant_value=3) + index = DataSymbol("idx", INTEGER_TYPE, is_constant=True, initial_value=3) symbol_table.add(index) array_type = ArrayType(REAL_TYPE, [5, 10, 15]) mat_symbol = DataSymbol("x", array_type) @@ -166,7 +166,8 @@ def test_get_array_bound(): new nodes are created each time the utility is called. ''' - scalar_symbol = DataSymbol("n", INTEGER_TYPE, constant_value=20) + scalar_symbol = DataSymbol("n", INTEGER_TYPE, is_constant=True, + initial_value=20) array_type = ArrayType(REAL_TYPE, [10, Reference(scalar_symbol)]) array_symbol = DataSymbol("x", array_type) reference = Reference(array_symbol) diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index e32758e5bb..be557aeb02 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -1988,18 +1988,17 @@ def make_constant(symbol_table, arg_position, value, 'arg_position' into a compile-time constant with value 'value'. - :param symbol_table: the symbol table for the kernel \ - holding the argument that is going to be modified. + :param symbol_table: the symbol table for the kernel holding + the argument that is going to be modified. :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable` - :param int arg_position: the argument's position in the \ - argument list. - :param value: the constant value that this argument is \ - going to be given. Its type depends on the type of the \ - argument. + :param int arg_position: the argument's position in the + argument list. + :param value: the constant value that this argument is going to + be given. Its type depends on the type of the argument. :type value: int, str or bool - :type str function_space: the name of the function space \ - if there is a function space associated with this \ - argument. Defaults to None. + :type str function_space: the name of the function space if there + is a function space associated with this argument. Defaults + to None. ''' arg_index = arg_position - 1 @@ -2033,7 +2032,7 @@ def make_constant(symbol_table, arg_position, value, # #321). orig_name = symbol.name local_symbol = DataSymbol(orig_name+"_dummy", INTEGER_TYPE, - constant_value=value) + is_constant=True, initial_value=value) symbol_table.add(local_symbol) symbol_table.swap_symbol_properties(symbol, local_symbol) @@ -2760,11 +2759,14 @@ def apply(self, node, options=None): # Convert the symbol to an argument and add it to the argument list current_arg_list = symtab.argument_list - if updated_sym.is_constant: + # An argument does not have an initial value. + was_constant = updated_sym.is_constant + updated_sym.is_constant = False + updated_sym.initial_value = None + if was_constant: # Imported constants lose the constant value but are read-only # TODO: When #633 and #11 are implemented, warn the user that # they should transform the constants to literal values first. - updated_sym.constant_value = None updated_sym.interface = ArgumentInterface( ArgumentInterface.Access.READ) else: