Reference Guide  2.5.0
call.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 # Authors R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab
35 # -----------------------------------------------------------------------------
36 
37 ''' This module contains the Call node implementation.'''
38 
39 from collections.abc import Iterable
40 
41 from psyclone.core import AccessType
42 from psyclone.psyir.nodes.statement import Statement
43 from psyclone.psyir.nodes.datanode import DataNode
44 from psyclone.psyir.nodes.reference import Reference
45 from psyclone.psyir.symbols import RoutineSymbol
46 from psyclone.errors import GenerationError
47 
48 
50  ''' Node representing a Call. This can be found as a standalone statement
51  or an expression.
52 
53  TODO #1437: The combined Statement and Expression implementation is simple
54  but it has some shortcomings that may need to be addressed.
55 
56  :param kwargs: additional keyword arguments provided to the PSyIR node.
57  :type kwargs: unwrapped dict.
58 
59  '''
60  # Textual description of the node.
61  _children_valid_format = "Reference, [DataNode]*"
62  _text_name = "Call"
63  _colour = "cyan"
64 
65  def __init__(self, **kwargs):
66  super().__init__(**kwargs)
67 
68  # The internal _argument_names list can be inconsistent with
69  # the order of the children. Use the property/methods
70  # internally and/or the _reconcile() method to make consistent
71  # when required.
72  self._argument_names_argument_names = []
73 
74  def __eq__(self, other):
75  '''
76  Checks whether two nodes are equal. Two Call nodes are equal
77  if their routine members are equal.
78 
79  :param object other: the object to check equality to.
80 
81  :returns: whether other is equal to self.
82  :rtype: bool
83  '''
84  is_eq = super().__eq__(other)
85  is_eq = is_eq and self.argument_namesargument_namesargument_names == other.argument_names
86 
87  return is_eq
88 
89  @classmethod
90  def create(cls, routine, arguments=()):
91  '''Create an instance of class cls given valid instances of a routine
92  symbol, and a list of child nodes (or name and node tuple) for
93  its arguments.
94 
95  :param routine: the routine that class cls calls.
96  :type routine: py:class:`psyclone.psyir.symbols.RoutineSymbol` |
97  py:class:`psyclone.psyir.nodes.Reference`
98  :param arguments: optional list of arguments for this call, these
99  can be PSyIR nodes or tuples of string,Node for named arguments.
100  :type arguments: Optional[Iterable[\
101  Union[:py:class:`psyclone.psyir.nodes.DataNode`,\
102  Tuple[str, :py:class:`psyclone.psyir.nodes.DataNode`]]]]
103 
104  :returns: an instance of cls.
105  :rtype: :py:class:`psyclone.psyir.nodes.Call` or a subclass thereof.
106 
107  :raises TypeError: if the routine argument is not a RoutineSymbol.
108  :raises GenerationError: if the arguments argument is not an Iterable.
109 
110  '''
111  if not isinstance(routine, (Reference, RoutineSymbol)):
112  raise TypeError(
113  f"The Call routine argument should be a Reference to a "
114  f"RoutineSymbol or a RoutineSymbol, but "
115  f"found '{type(routine).__name__}'.")
116 
117  if not isinstance(arguments, Iterable):
118  raise GenerationError(
119  f"Call.create 'arguments' argument should be an Iterable but "
120  f"found '{type(arguments).__name__}'.")
121 
122  call = cls()
123  if isinstance(routine, Reference):
124  call.addchild(routine)
125  else:
126  call.addchild(Reference(routine))
127  if arguments:
128  cls._add_args_add_args(call, arguments)
129  return call
130 
131  @staticmethod
132  def _add_args(call, arguments):
133  '''Internal utility method to add arguments to a call node. These are
134  added as child nodes.
135 
136  :param call: the supplied call node.
137  :type call: :py:class:`psyclone.psyir.nodes.Call`
138  :param arguments: list of arguments for this call, these
139  can be PSyIR nodes or tuples of string,Node for named arguments.
140  :type arguments: Iterable[
141  Union[:py:class:`psyclone.psyir.nodes.DataNode`,
142  Tuple[str, :py:class:`psyclone.psyir.nodes.DataNode`]]]
143 
144  :raises GenerationError: if the contents of the arguments
145  argument are not in the expected form or of the expected
146  type.
147 
148  '''
149  for arg in arguments:
150  name = None
151  if isinstance(arg, tuple):
152  if not len(arg) == 2:
153  raise GenerationError(
154  f"If a child of the children argument in create "
155  f"method of Call class is a tuple, it's "
156  f"length should be 2, but found {len(arg)}.")
157  if not isinstance(arg[0], str):
158  raise GenerationError(
159  f"If a child of the children argument in create "
160  f"method of Call class is a tuple, its first "
161  f"argument should be a str, but found "
162  f"{type(arg[0]).__name__}.")
163  name, arg = arg
164  call.append_named_arg(name, arg)
165 
166  def append_named_arg(self, name, arg):
167  '''Append a named argument to this call.
168 
169  :param name: the argument name.
170  :type name: Optional[str]
171  :param arg: the argument expression.
172  :type arg: :py:class:`psyclone.psyir.nodes.DataNode`
173 
174  :raises ValueError: if the name argument is already used \
175  for an existing argument.
176 
177  '''
178  if name is not None:
179  # Avoid circular import.
180  # pylint: disable=import-outside-toplevel
181  from psyclone.psyir.frontend.fortran import FortranReader
182  FortranReader.validate_name(name)
183  for check_name in self.argument_namesargument_namesargument_names:
184  if check_name and check_name.lower() == name.lower():
185  raise ValueError(
186  f"The value of the name argument ({name}) in "
187  f"'append_named_arg' in the 'Call' node is "
188  f"already used for a named argument.")
189  self._argument_names_argument_names.append((id(arg), name))
190  self.childrenchildrenchildren.append(arg)
191 
192  def insert_named_arg(self, name, arg, index):
193  '''Insert a named argument to the call.
194 
195  :param name: the argument name.
196  :type name: Optional[str]
197  :param arg: the argument expression.
198  :type arg: :py:class:`psyclone.psyir.nodes.DataNode`
199  :param int index: where in the argument list to insert the \
200  named argument.
201 
202  :raises ValueError: if the name argument is already used \
203  for an existing argument.
204  :raises TypeError: if the index argument is the wrong type.
205 
206  '''
207  if name is not None:
208  # Avoid circular import.
209  # pylint: disable=import-outside-toplevel
210  from psyclone.psyir.frontend.fortran import FortranReader
211  FortranReader.validate_name(name)
212  for check_name in self.argument_namesargument_namesargument_names:
213  if check_name and check_name.lower() == name.lower():
214  raise ValueError(
215  f"The value of the name argument ({name}) in "
216  f"'insert_named_arg' in the 'Call' node is "
217  f"already used for a named argument.")
218  if not isinstance(index, int):
219  raise TypeError(
220  f"The 'index' argument in 'insert_named_arg' in the "
221  f"'Call' node should be an int but found "
222  f"{type(index).__name__}.")
223  self._argument_names_argument_names.insert(index, (id(arg), name))
224  # The n'th argument is placed at the n'th+1 children position
225  # because the 1st child is the routine reference
226  self.childrenchildrenchildren.insert(index + 1, arg)
227 
228  def replace_named_arg(self, existing_name, arg):
229  '''Replace one named argument node with another node keeping the
230  same name.
231 
232  :param str existing_name: the argument name.
233  :param arg: the argument expression.
234  :type arg: :py:class:`psyclone.psyir.nodes.DataNode`
235 
236  :raises TypeError: if the name argument is the wrong type.
237  :raises ValueError: if the name argument is already used \
238  for an existing argument.
239  :raises TypeError: if the index argument is the wrong type.
240 
241  '''
242  if not isinstance(existing_name, str):
243  raise TypeError(
244  f"The 'name' argument in 'replace_named_arg' in the "
245  f"'Call' node should be a string, but found "
246  f"{type(existing_name).__name__}.")
247  index = 0
248  for _, name in self._argument_names_argument_names:
249  if name is not None and name.lower() == existing_name:
250  break
251  index += 1
252  else:
253  raise ValueError(
254  f"The value of the existing_name argument ({existing_name}) "
255  f"in 'replace_named_arg' in the 'Call' node was not found "
256  f"in the existing arguments.")
257  # The n'th argument is placed at the n'th+1 children position
258  # because the 1st child is the routine reference
259  self.childrenchildrenchildren[index + 1] = arg
260  self._argument_names_argument_names[index] = (id(arg), existing_name)
261 
262  @staticmethod
263  def _validate_child(position, child):
264  '''
265  :param int position: the position to be validated.
266  :param child: a child to be validated.
267  :type child: :py:class:`psyclone.psyir.nodes.Node`
268 
269  :return: whether the given child and position are valid for this node.
270  :rtype: bool
271 
272  '''
273  if position == 0:
274  return isinstance(child, Reference)
275  return isinstance(child, DataNode)
276 
277  def reference_accesses(self, var_accesses):
278  '''
279  Updates the supplied var_accesses object with information on the
280  arguments passed to this call.
281 
282  TODO #446 - all arguments that are passed by reference are currently
283  marked as having READWRITE access (unless we know that the routine is
284  PURE). We could do better than this if we have the PSyIR of the called
285  Routine.
286 
287  :param var_accesses: VariablesAccessInfo instance that stores the
288  information about variable accesses.
289  :type var_accesses: :py:class:`psyclone.core.VariablesAccessInfo`
290 
291  '''
292  if self.is_pureis_pure:
293  # If the called routine is pure then any arguments are only
294  # read.
295  default_access = AccessType.READ
296  else:
297  # We conservatively default to READWRITE otherwise (TODO #446).
298  default_access = AccessType.READWRITE
299 
300  # TODO #2271: This may skip references in inner expressions of
301  # structure calls, but to implement properly we new a new kind of
302  # AccessType that represents being called (USED but not READ, maybe
303  # the same that we need for INQUIRY type attributes?)
304  for arg in self.argumentsarguments:
305  if isinstance(arg, Reference):
306  # This argument is pass-by-reference.
307  sig, indices_list = arg.get_signature_and_indices()
308  var_accesses.add_access(sig, default_access, arg)
309  # Any symbols referenced in any index expressions are READ.
310  for indices in indices_list:
311  for idx in indices:
312  idx.reference_accesses(var_accesses)
313  else:
314  # This argument is not a Reference so continue to walk down the
315  # tree. (e.g. it could be/contain a Call to
316  # an impure routine in which case any arguments to that Call
317  # will have READWRITE access.)
318  arg.reference_accesses(var_accesses)
319  # Make sure that the next statement will be on the next location
320  var_accesses.next_location()
321 
322  @property
323  def routine(self):
324  '''
325  :returns: the routine reference that this call calls.
326  :rtype: Optional[py:class:`psyclone.psyir.nodes.Reference`]
327  '''
328  if len(self._children_children) >= 1:
329  return self.childrenchildrenchildren[0]
330  return None
331 
332  @property
333  def arguments(self):
334  '''
335  :returns: the children of this node that represent its arguments.
336  :rtype: list[py:class:`psyclone.psyir.nodes.DataNode`]
337  '''
338  if len(self._children_children) >= 2:
339  return self.childrenchildrenchildren[1:]
340  return []
341 
342  @property
343  def is_elemental(self):
344  '''
345  :returns: whether the routine being called is elemental (provided with
346  an input array it will apply the operation individually to each of
347  the array elements and return an array with the results). If this
348  information is not known then it returns None.
349  :rtype: NoneType | bool
350  '''
351  if self.routineroutine and self.routineroutine.symbol:
352  return self.routineroutine.symbol.is_elemental
353  return None
354 
355  @property
356  def is_pure(self):
357  '''
358  :returns: whether the routine being called is pure (guaranteed to \
359  return the same result when provided with the same argument \
360  values). If this information is not known then it returns None.
361  :rtype: NoneType | bool
362  '''
363  if self.routineroutine and self.routineroutine.symbol:
364  return self.routineroutine.symbol.is_pure
365  return None
366 
368  '''
369  :returns: whether this call is available on an accelerated device.
370  :rtype: bool
371 
372  '''
373  return False
374 
375  @property
376  def argument_names(self):
377  '''
378  :returns: a list with the name of each argument. If the entry is \
379  None then the argument is a positional argument.
380  :rtype: List[Optional[str]]
381  '''
382  self._reconcile_reconcile()
383  return [entry[1] for entry in self._argument_names_argument_names]
384 
385  def _reconcile(self):
386  '''Update the _argument_names values in case child arguments have been
387  removed, added, or re-ordered.
388 
389  '''
390  new_argument_names = []
391  for child in self.argumentsarguments:
392  for arg in self._argument_names_argument_names:
393  if id(child) == arg[0]:
394  new_argument_names.append(arg)
395  break
396  else:
397  new_argument_names.append((id(child), None))
398  self._argument_names_argument_names = new_argument_names
399 
400  def node_str(self, colour=True):
401  '''
402  Construct a text representation of this node, optionally containing
403  colour control codes.
404 
405  :param bool colour: whether or not to include colour control codes.
406 
407  :returns: description of this PSyIR node.
408  :rtype: str
409 
410  '''
411  return (f"{self.coloured_name(colour)}"
412  f"[name='{self.routine.debug_string()}']")
413 
414  def __str__(self):
415  return self.node_strnode_strnode_str(False)
416 
417  def copy(self):
418  '''Return a copy of this node. This is a bespoke implementation for
419  a Call node that ensures that any internal id's are
420  consistent before and after copying.
421 
422  :returns: a copy of this node and its children.
423  :rtype: :py:class:`psyclone.psyir.node.Node`
424 
425  '''
426  # ensure _argument_names is consistent with actual arguments
427  # before copying.
428  self._reconcile_reconcile()
429  # copy
430  new_copy = super().copy()
431  # Fix invalid id's in _argument_names after copying.
432  # pylint: disable=protected-access
433  new_list = []
434  for idx, child in enumerate(new_copy.arguments):
435  my_tuple = (id(child), new_copy._argument_names[idx][1])
436  new_list.append(my_tuple)
437  new_copy._argument_names = new_list
438 
439  return new_copy
def append_named_arg(self, name, arg)
Definition: call.py:166
def _add_args(call, arguments)
Definition: call.py:132
def is_available_on_device(self)
Definition: call.py:367
def __eq__(self, other)
Definition: call.py:74
def node_str(self, colour=True)
Definition: call.py:400
def insert_named_arg(self, name, arg, index)
Definition: call.py:192
def replace_named_arg(self, existing_name, arg)
Definition: call.py:228
def create(cls, routine, arguments=())
Definition: call.py:90
def reference_accesses(self, var_accesses)
Definition: call.py:277
def children(self, my_children)
Definition: node.py:935
def node_str(self, colour=True)
Definition: node.py:483