Reference Guide  2.5.0
routine.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2020-2024, Science and Technology Facilities Council.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are met:
9 #
10 # * Redistributions of source code must retain the above copyright notice, this
11 # list of conditions and the following disclaimer.
12 #
13 # * Redistributions in binary form must reproduce the above copyright notice,
14 # this list of conditions and the following disclaimer in the documentation
15 # and/or other materials provided with the distribution.
16 #
17 # * Neither the name of the copyright holder nor the names of its
18 # contributors may be used to endorse or promote products derived from
19 # this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 # POSSIBILITY OF SUCH DAMAGE.
33 # -----------------------------------------------------------------------------
34 # Author: A. R. Porter, STFC Daresbury Lab
35 # Modified by: R. W. Ford, STFC Daresbury Lab
36 # Modified by: S. Siso, STFC Daresbury Lab
37 # MOdified by: J. Henrichs, Bureau of Meteorology
38 # -----------------------------------------------------------------------------
39 
40 ''' This module contains the Routine node implementation.'''
41 
42 from psyclone.psyir.nodes.commentable_mixin import CommentableMixin
43 from psyclone.psyir.nodes.node import Node
44 from psyclone.psyir.nodes.schedule import Schedule
45 from psyclone.psyir.symbols import DataSymbol, RoutineSymbol, NoType
46 from psyclone.psyir.symbols.symbol_table import SymbolTable
47 
48 
50  '''
51  A sub-class of a Schedule that represents a subroutine, function or
52  program unit.
53 
54  :param str name: the name of this routine.
55  :param bool is_program: whether this Routine represents the entry point \
56  into a program (e.g. Fortran Program or C main()).
57  :param kwargs: additional keyword arguments provided to the super class.
58  :type kwargs: unwrapped dict.
59 
60  :raises TypeError: if any of the supplied arguments are of the wrong type.
61 
62  '''
63  # Textual description of the node.
64  _children_valid_format = "[Statement]*"
65  _text_name = "Routine"
66 
67  def __init__(self, name, is_program=False, **kwargs):
68  super().__init__(**kwargs)
69 
70  self._return_symbol_return_symbol = None
71  self._name_name = None
72  # Name is set-up by the name setter property
73  self.namenamenamename = name
74 
75  if not isinstance(is_program, bool):
76  raise TypeError(f"Routine argument 'is_program' must be a bool "
77  f"but got '{type(is_program).__name__}'")
78  self._is_program_is_program = is_program
79 
80  def __eq__(self, other):
81  '''
82  Checks whether two nodes are equal. Two Routine nodes are equal
83  if they have the same name, same return symbol, same properties and
84  the inherited __eq__ is True.
85 
86  :param object other: the object to check equality to.
87 
88  :returns: whether other is equal to self.
89  :rtype: bool
90  '''
91  is_eq = super().__eq__(other)
92  is_eq = is_eq and self.namenamenamename == other.name
93  is_eq = is_eq and self.is_programis_programis_program == other.is_program
94  is_eq = is_eq and self.return_symbolreturn_symbolreturn_symbolreturn_symbol == other.return_symbol
95 
96  return is_eq
97 
98  @classmethod
99  def create(cls, name, symbol_table, children, is_program=False,
100  return_symbol_name=None):
101  # pylint: disable=too-many-arguments
102  '''Create an instance of the supplied class given a name, a symbol
103  table and a list of child nodes. This is implemented as a classmethod
104  so that it is able to act as a Factory for subclasses - e.g. it
105  will create a KernelSchedule if called from KernelSchedule.create().
106 
107  :param str name: the name of the Routine (or subclass).
108  :param symbol_table: the symbol table associated with this Routine.
109  :type symbol_table: :py:class:`psyclone.psyGen.SymbolTable`
110  :param children: a list of PSyIR nodes contained in the Routine.
111  :type children: list of :py:class:`psyclone.psyir.nodes.Node`
112  :param bool is_program: whether this Routine represents the entry \
113  point into a program (i.e. Fortran Program or C main()).
114  :param str return_symbol_name: name of the symbol that holds the \
115  return value of this routine (if any). Must be present in the \
116  supplied symbol table.
117 
118  :returns: an instance of `cls`.
119  :rtype: :py:class:`psyclone.psyGen.Routine` or subclass
120 
121  :raises TypeError: if the arguments to the create method \
122  are not of the expected type.
123 
124  '''
125  if not isinstance(name, str):
126  raise TypeError(
127  f"name argument in create method of Routine class "
128  f"should be a string but found '{type(name).__name__}'.")
129  if not isinstance(symbol_table, SymbolTable):
130  raise TypeError(
131  f"symbol_table argument in create method of Routine class "
132  f"should be a SymbolTable but found "
133  f"'{type(symbol_table).__name__}'.")
134  if not isinstance(children, list):
135  raise TypeError(
136  f"children argument in create method of Routine class "
137  f"should be a list but found '{type(children).__name__}'.")
138  for child in children:
139  if not isinstance(child, Node):
140  raise TypeError(
141  f"child of children argument in create method of "
142  f"Routine class should be a PSyIR Node but "
143  f"found '{type(child).__name__}'.")
144 
145  routine = cls(name, is_program=is_program, symbol_table=symbol_table)
146  routine.children = children
147  if return_symbol_name:
148  routine.return_symbol = routine.symbol_table.lookup(
149  return_symbol_name, scope_limit=routine)
150  return routine
151 
152  def node_str(self, colour=True):
153  ''' Returns the name of this node with (optional) control codes
154  to generate coloured output in a terminal that supports it.
155 
156  :param bool colour: whether or not to include colour control codes.
157 
158  :returns: description of this node, possibly coloured.
159  :rtype: str
160  '''
161  return self.coloured_namecoloured_name(colour) + "[name:'" + self.namenamenamename + "']"
162 
163  @property
164  def dag_name(self):
165  '''
166  :returns: the name of this node in the dag.
167  :rtype: str
168  '''
169  return "_".join(["routine", self.namenamenamename, str(self.START_POSITIONSTART_POSITION)])
170 
171  @property
172  def name(self):
173  '''
174  :returns: the name of this Routine.
175  :rtype: str
176  '''
177  return self._name_name
178 
179  @name.setter
180  def name(self, new_name):
181  '''
182  Sets a new name for the Routine.
183 
184  TODO #1200 this node should only hold a reference to the corresponding
185  RoutineSymbol and get its name from there.
186 
187  :param str new_name: new name for the Routine.
188 
189  :raises TypeError: if new_name is not a string.
190  :raises KeyError: if there already is a different named symbol with \
191  the 'own_routine_tag' in the symbol_table.
192 
193  '''
194  if not isinstance(new_name, str):
195  raise TypeError(f"Routine name must be a str but got "
196  f"'{type(new_name).__name__}'")
197  # TODO #1200 The name is duplicated in the _name attribute and the
198  # symbol.name that there is in the local symbol table. This setter
199  # updates both but note that a better solution is needed because
200  # renaming the symbol_table symbol alone would make it inconsistent.
201  if not self._name_name:
202  # If the 'own_routine_symbol' tag already exist check that is
203  # consistent with the given routine name.
204  if 'own_routine_symbol' in self.symbol_tablesymbol_tablesymbol_table.tags_dict:
205  existing_symbol = self.symbol_tablesymbol_tablesymbol_table.lookup_with_tag(
206  'own_routine_symbol', scope_limit=self)
207  if existing_symbol.name.lower() == new_name.lower():
208  self._name_name = new_name
209  return # The preexisting symbol already matches
210  # Otherwise raise an exception
211  raise KeyError(
212  f"Can't assign '{new_name}' as the routine name because "
213  f"its symbol table contains a symbol ({existing_symbol}) "
214  f"already tagged as 'own_routine_symbol'.")
215 
216  self._name_name = new_name
217  # Since the constructor can not mark methods as functions directly
218  # the symbol will always start being NoType and must be updated
219  # if a return_value type is provided.
220  self.symbol_tablesymbol_tablesymbol_table.add(RoutineSymbol(new_name, NoType()),
221  tag='own_routine_symbol')
222  elif self._name_name != new_name:
223  symbol = self.symbol_tablesymbol_tablesymbol_table.lookup(self._name_name)
224  self._name_name = new_name
225  self.symbol_tablesymbol_tablesymbol_table.rename_symbol(symbol, new_name)
226 
227  def __str__(self):
228  result = self.node_strnode_strnode_str(False) + ":\n"
229  for entity in self._children_children:
230  result += str(entity) + "\n"
231  result += "End " + self.coloured_namecoloured_name(False)
232  return result
233 
234  @property
235  def is_program(self):
236  '''
237  :returns: whether this Routine represents the entry point into a \
238  program (e.g. is a Fortran Program or a C main()).
239  :rtype: bool
240  '''
241  return self._is_program_is_program
242 
243  @property
244  def return_symbol(self):
245  '''
246  :returns: the symbol which will hold the return value of this Routine \
247  or None if the Routine is not a function.
248  :rtype: :py:class:`psyclone.psyir.symbols.DataSymbol` or NoneType
249  '''
250  return self._return_symbol_return_symbol
251 
252  @return_symbol.setter
253  def return_symbol(self, value):
254  '''
255  Setter for the return-symbol of this Routine node.
256 
257  :param value: the symbol holding the value that the routine returns.
258  :type value: :py:class:`psyclone.psyir.symbols.DataSymbol`
259 
260  :raises TypeError: if the supplied value is not a DataSymbol.
261  :raises KeyError: if the supplied symbol is not a local entry in the \
262  symbol table of this Routine.
263  '''
264  if not isinstance(value, DataSymbol):
265  raise TypeError(f"Routine return-symbol should be a DataSymbol "
266  f"but found '{type(value).__name__}'.")
267  if value not in self.symbol_tablesymbol_tablesymbol_table.datasymbols:
268  raise KeyError(
269  f"For a symbol to be a return-symbol, it must be present in "
270  f"the symbol table of the Routine but '{value.name}' is not.")
271  self._return_symbol_return_symbol = value
272  # The routine symbol must be updated accordingly, this is because the
273  # function datatype is provided by the type of the return symbol which
274  # may be given after the Routine is created.
275  self.symbol_tablesymbol_tablesymbol_table.lookup(self._name_name).datatype = value.datatype
276 
277  def _refine_copy(self, other):
278  ''' Refine the object attributes when a shallow copy is not the most
279  appropriate operation during a call to the copy() method.
280 
281  :param other: object we are copying from.
282  :type other: :py:class:`psyclone.psyir.node.Routine`
283 
284  '''
285  super()._refine_copy(other)
286  if other.return_symbol is not None:
287  self.return_symbolreturn_symbolreturn_symbolreturn_symbol = self.symbol_tablesymbol_tablesymbol_table.lookup(
288  other.return_symbol.name)
289 
290 
291 # For automatic documentation generation
292 __all__ = ["Routine"]
def node_str(self, colour=True)
Definition: node.py:483
def coloured_name(self, colour=True)
Definition: node.py:453
def node_str(self, colour=True)
Definition: routine.py:152
def create(cls, name, symbol_table, children, is_program=False, return_symbol_name=None)
Definition: routine.py:100