Reference Guide  2.5.0
psyir.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2021-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: R. W. Ford, STFC Daresbury Lab
35 # Modified: J. Henrichs, Bureau of Meteorology
36 # A. R. Porter, STFC Daresbury Lab
37 
38 '''This module contains PSyclone Algorithm-layer-specific PSyIR classes.
39 
40 '''
41 import re
42 
43 from psyclone.errors import GenerationError, InternalError
44 from psyclone.psyir.frontend.fortran import FortranReader
45 from psyclone.psyir.nodes import (Call, Reference, DataNode,
46  Routine, Container, FileContainer)
47 from psyclone.psyir.symbols import DataTypeSymbol
48 
49 
50 class AlgorithmInvokeCall(Call):
51  '''An invoke call in a PSyclone Algorithm layer.
52 
53  :param invoke_routine_symbol: the routine that this call calls.
54  :type invoke_routine_symbol: \
55  py:class:`psyclone.psyir.symbols.RoutineSymbol`
56  :param int index: the position of this invoke call relative to \
57  other invokes in the algorithm layer.
58  :param parent: optional parent of this node in the PSyIR. Defaults \
59  to None.
60  :type parent: sub-class of :py:class:`psyclone.psyir.nodes.Node` \
61  or NoneType
62  :param Optional[str] name: an optional name, describing the \
63  AlgorithmInvokeCall. Defaults to None.
64 
65  :raises TypeError: if the index argument is not an integer.
66  :raises ValueError: if the index argument is negative.
67  :raises ValueError: if an invalid name is supplied.
68 
69  '''
70  _children_valid_format = "Reference, [KernelFunctor]*"
71  _text_name = "AlgorithmInvokeCall"
72  _colour = "green"
73 
74  def __init__(self, invoke_routine_symbol, index, parent=None, name=None):
75  super().__init__(parent=parent)
76  self.addchild(Reference(invoke_routine_symbol))
77 
78  if not isinstance(index, int):
79  raise TypeError(
80  f"AlgorithmInvokeCall index argument should be an int but "
81  f"found '{type(index).__name__}'.")
82  if index < 0:
83  raise ValueError(
84  f"AlgorithmInvokeCall index argument should be a non-negative "
85  f"integer but found {index}.")
86  try:
87  if name:
88  FortranReader.validate_name(name)
89  except (TypeError, ValueError) as err:
90  raise ValueError(
91  f"Error with AlgorithmInvokeCall name argument: "
92  f"{err}") from err
93 
94  self._index_index = index
95  # Keep the root names as these will also be needed by the
96  # PSy-layer to use as tags to pull out the actual names from
97  # the algorithm symbol table, once issue #753 is complete.
98  # They are public properties because they are needed in
99  # AlgInvoke2PSyCallTrans.
100  self.psylayer_routine_root_namepsylayer_routine_root_name = None
101  self.psylayer_container_root_namepsylayer_container_root_name = None
102  self._name_name = name
103 
104  @classmethod
105  def create(cls, routine, arguments, index, name=None):
106  # pylint: disable=arguments-differ
107  '''Create an instance of the calling class given valid instances of a
108  routine symbol, a list of child nodes for its arguments, an
109  index and an optional name.
110 
111  :param routine: the routine that the calling class calls.
112  :type routine: py:class:`psyclone.psyir.symbols.RoutineSymbol`
113  :param arguments: the arguments to this routine. These are \
114  added as child nodes.
115  :type arguments: list of :py:class:`psyclone.psyir.nodes.DataNode`
116  :param int index: the position of this invoke call relative to \
117  other invokes in the algorithm layer.
118  :param Optional[str] name: a string naming/describing the invoke
119  or None if one is not provided. This is converted to lower case
120  and used to create the name of the routine that replaces the
121  invoke. It must be a valid Fortran name. Defaults to None.
122 
123  :raises GenerationError: if the arguments argument is not a \
124  list.
125 
126  :returns: an instance of the calling class.
127  :rtype: :py:class:`psyclone.psyir.nodes.AlgorithmInvokeCall` \
128  or a subclass thereof.
129 
130  '''
131  if not isinstance(arguments, list):
132  raise GenerationError(
133  f"AlgorithmInvokeCall create arguments argument should be a "
134  f"list but found '{type(arguments).__name__}'.")
135 
136  # Convert name to lowercase if provided.
137  if name:
138  # We have to validate it first as that checks (amongst other
139  # things) that it is a str.
140  FortranReader.validate_name(name)
141  lwr_name = name.lower()
142  else:
143  lwr_name = None
144  call = cls(routine, index, name=lwr_name)
145  call.children.extend(arguments)
146  return call
147 
148  @staticmethod
149  def _validate_child(position, child):
150  '''
151  :param int position: the position to be validated.
152  :param child: a child to be validated.
153  :type child: :py:class:`psyclone.psyir.nodes.Node`
154 
155  :returns: whether the given child and position are valid for this node.
156  :rtype: bool
157 
158  '''
159  if position == 0:
160  return isinstance(child, Reference)
161  return isinstance(child, KernelFunctor)
162 
163  def node_str(self, colour=True):
164  '''Construct a text representation of this node, optionally
165  containing colour control codes. Specialise as this node has
166  an additional name argument.
167 
168  :param bool colour: whether or not to include colour control \
169  codes. Optional argument that defaults to True.
170 
171  :returns: description of this PSyIR node.
172  :rtype: str
173 
174  '''
175  return f"{self.coloured_name(colour)}[name=\"{self._name}\"]"
176 
177  def _def_routine_root_name(self):
178  '''Internal method that returns the proposed language-level routine
179  name.
180 
181  :returns: the proposed processed routine name for this invoke.
182  :rtype: str
183 
184  :raises TypeError: if the name provided in the invoke call is invalid.
185 
186  '''
187  if self._name_name:
188  routine_root_name = self._name_name.lower().strip()
189  if routine_root_name[0] == '"' and routine_root_name[-1] == '"' \
190  or \
191  routine_root_name[0] == "'" and routine_root_name[-1] == "'":
192  # fparser2 (issue #295) currently includes quotes as
193  # part of a string, so strip them out.
194  routine_root_name = routine_root_name[1:-1].strip()
195  routine_root_name = routine_root_name.replace(" ", "_")
196  # Check that the name is a valid routine name
197  pattern = re.compile(r"^[a-zA-Z]\w*$", re.ASCII)
198  if not pattern.match(routine_root_name):
199  raise TypeError(
200  f"AlgorithmInvokeCall:_def_routine_root_name() the "
201  f"(optional) name of an invoke must be a string "
202  f"containing a valid name (with any spaces replaced by "
203  f"underscores) but found '{routine_root_name}'.")
204  if not routine_root_name.startswith("invoke"):
205  routine_root_name = f"invoke_{routine_root_name}"
206  else:
207  routine_root_name = f"invoke_{self._index}"
208  if len(self.arguments) == 1:
209  # Add the name of the kernel if there is only one call
210  routine_root_name += "_" + self.arguments[0].name
211  return routine_root_name
212 
214  '''If the PSy-layer routine and container root names have not been
215  created, then create them. The invoke root name is based on
216  the position of this node (compared to other nodes of the same
217  type) in the PSyIR tree. Note, we do not create and store
218  symbols, as the container name needs to be consistent between
219  different invoke calls and we have no way of knowing whether
220  one has already been created without the symbol being stored
221  in the symbol table, and we don't want to add anything related
222  to the lower level PSyIR to the symbol table before lowering.
223 
224  :raises InternalError: if no Routine or Container is found in \
225  the PSyIR tree containing this node.
226 
227  '''
228  if not self.psylayer_routine_root_namepsylayer_routine_root_name:
229  self.psylayer_routine_root_namepsylayer_routine_root_name = self._def_routine_root_name_def_routine_root_name()
230 
231  if not self.psylayer_container_root_namepsylayer_container_root_name:
232  # The PSy-layer module naming logic (in algorithm.py) finds
233  # the first program, module, subroutine or function in the
234  # parse tree and uses that name for the container name. Here
235  # we temporarily replicate this functionality. Eventually we
236  # will merge. Note, a better future solution could be to use
237  # the closest ancestor routine instead.
238  for node in self.root.walk((Routine, Container)):
239  if not isinstance(node, FileContainer):
240  self.psylayer_container_root_namepsylayer_container_root_name = \
241  self._def_container_root_name_def_container_root_name(node)
242  return
243  raise InternalError("No Routine or Container node found.")
244 
245  @staticmethod
246  def _def_container_root_name(node):
247  '''
248  :returns: the root name to use for the container.
249  :rtype: str
250  '''
251  return f"psy_{node.name}"
252 
253 
254 class KernelFunctor(Reference):
255  '''Object containing a kernel call, a description of its required
256  interface and the arguments to be passed to it.
257 
258  :param symbol: the functor symbol.
259  :type symbol: :py:class:`psyclone.psyir.symbols.Symbol`
260  :param parent: the parent node of this functor instance.
261  :type parent: :py:class:`psyclone.psyir.nodes.Node` or NoneType
262 
263  '''
264  _children_valid_format = "[DataNode]*"
265  _text_name = "KernelFunctor"
266 
267  def __init__(self, symbol, parent=None):
268  super().__init__(symbol, parent=parent)
269 
270  if not isinstance(symbol, DataTypeSymbol):
271  raise TypeError(
272  f"KernelFunctor symbol argument should be a DataTypeSymbol "
273  f"but found '{type(symbol).__name__}'.")
274 
275  @classmethod
276  def create(cls, symbol, arguments):
277  '''Create an instance of the calling class given valid instances of a
278  DataTypeSymbol and a list of child nodes for its arguments.
279 
280  :param symbol: the name of the kernel type that this object \
281  references.
282  :type symbol: py:class:`psyclone.psyir.symbols.DataTypeSymbol`
283  :param arguments: the arguments to this routine. These are \
284  added as child nodes.
285  :type arguments: list of :py:class:`psyclone.psyir.nodes.DataNode`
286 
287  :returns: an instance of the calling class.
288  :rtype: :py:class:`psyclone.psyir.nodes.Call` or subclass thereof.
289 
290  '''
291  if not isinstance(symbol, DataTypeSymbol):
292  raise GenerationError(
293  f"KernelFunctor create() symbol argument should be a "
294  f"DataTypeSymbol but found '{type(symbol).__name__}'.")
295  if not isinstance(arguments, list):
296  raise GenerationError(
297  f"KernelFunctor create() arguments argument should be a list "
298  f"but found '{type(arguments).__name__}'.")
299 
300  call = cls(symbol)
301  call.children = arguments
302  return call
303 
304  @staticmethod
305  def _validate_child(position, child):
306  '''
307  :param int position: the position to be validated.
308  :param child: a child to be validated.
309  :type child: :py:class:`psyclone.psyir.nodes.Node`
310 
311  :return: whether the given child and position are valid for this node.
312  :rtype: bool
313 
314  '''
315  return isinstance(child, DataNode)
316 
317 
318 __all__ = [
319  'AlgorithmInvokeCall',
320  'KernelFunctor']
def create(cls, routine, arguments, index, name=None)
Definition: psyir.py:105