Reference Guide  2.5.0
signature.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2022-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 J. Henrichs, Bureau of Meteorology
35 # Modified by N. Nobre, STFC Daresbury Lab
36 # Modified by S. Siso, STFC Daresbury Lab
37 # -----------------------------------------------------------------------------
38 
39 '''This module provides management of variable access information.'''
40 
41 from psyclone.errors import InternalError
42 from psyclone.psyir.symbols import DataSymbol, INTEGER_TYPE
43 
44 
45 # =============================================================================
46 class Signature:
47  '''Given a variable access of the form ``a(i,j)%b(k,l)%c``, the signature
48  of this access is the tuple ``(a,b,c)``. For a simple scalar variable
49  ``a`` the signature would just be ``(a,)``.
50  The signature is the key used in `VariablesAccessInfo`. In order to make
51  sure two different signature objects containing the same variable
52  can be used as a key, this class implements `__hash__` and other special
53  functions.
54  The constructor also supports appending an existing signature to this
55  new signature using the `sub_sig` argument. This is used in
56  StructureReference to assemble the overall signature of a structure
57  access.
58 
59  :param variable: the variable that is accessed.
60  :type variable: str or tuple of str or list of str
61 
62  :param sub_sig: a signature that is to be added to this new signature.
63  :type sub_sig: :py:class:`psyclone.core.Signature`
64 
65  '''
66  def __init__(self, variable, sub_sig=None):
67  if sub_sig:
68  sub_tuple = sub_sig._signature
69  else:
70  # null-tuple
71  sub_tuple = ()
72  if isinstance(variable, str):
73  self._signature_signature = tuple(variable.split("%")) + sub_tuple
74  elif isinstance(variable, tuple):
75  self._signature_signature = variable + sub_tuple
76  elif isinstance(variable, list):
77  self._signature_signature = tuple(variable) + sub_tuple
78  elif isinstance(variable, Signature):
79  self._signature_signature = variable._signature + sub_tuple
80  else:
81  raise InternalError(f"Got unexpected type "
82  f"'{type(variable).__name__}' in Signature "
83  f"constructor")
84 
85  # ------------------------------------------------------------------------
86  @property
87  def is_structure(self):
88  ''':returns: True if this signature represents a structure.
89  :rtype: bool
90  '''
91  return len(self._signature_signature) > 1
92 
93  # ------------------------------------------------------------------------
94  def __len__(self):
95  ''':returns: the number of components of this signature.
96  :rtype: int'''
97  return len(self._signature_signature)
98 
99  # ------------------------------------------------------------------------
100  def __getitem__(self, indx):
101  if isinstance(indx, slice):
102  return Signature(self._signature_signature[indx])
103  return self._signature_signature[indx]
104 
105  # ------------------------------------------------------------------------
106  def __str__(self):
107  return "%".join(self._signature_signature)
108 
109  # ------------------------------------------------------------------------
110  def to_language(self, component_indices=None, language_writer=None):
111  # pylint: disable=too-many-locals
112  '''Converts this signature with the provided indices to a string
113  in the selected language.
114 
115  TODO 1320 This subroutine can be removed when we stop supporting
116  strings - then we can use a PSyIR writer for the ReferenceNode
117  to provide the right string.
118 
119  :param component_indices: the indices for each component of \
120  the signature.
121  :type component_indices: None (default is scalar access), or \
122  :py:class:`psyclone.core.component_indices.ComponentIndices`
123  :param language_writer: a backend visitor to convert PSyIR \
124  expressions to a representation in the selected language. \
125  This is used when creating error and warning messages.
126  :type language_writer: None (default is Fortran), or an instance of \
127  :py:class:`psyclone.psyir.backend.language_writer.LanguageWriter`
128 
129  :raises InternalError: if the number of components in this signature \
130  is different from the number of indices in component_indices.
131  '''
132 
133  # Avoid circular import
134  # pylint: disable=import-outside-toplevel
135  from psyclone.core import ComponentIndices
136 
137  # By default, if component_indices is None, we assume a scalar access:
138  if component_indices is None:
139  component_indices = ComponentIndices([[]] * len(self))
140 
141  # Check if number of components between self and component_indices
142  # is consistent:
143  if len(self._signature_signature) != len(component_indices):
144  raise InternalError(f"Signature '{self}' has {len(self)} "
145  f"components, but component_indices "
146  f"{component_indices} has "
147  f"{len(component_indices)}.")
148  # Avoid circular import
149  # pylint: disable=import-outside-toplevel
150  from psyclone.psyir.backend.debug_writer import DebugWriter
151  from psyclone.psyir.nodes import Literal, Node, Reference
152 
153  if language_writer is None:
154  writer = DebugWriter()
155  else:
156  writer = language_writer
157 
158  # out_list collects the string representation of the components
159  # including indices
160  out_list = []
161  for i, component in enumerate(self._signature_signature):
162  indices = component_indices[i]
163  if not indices:
164  out_list.append(component)
165  else:
166  # If there are indices, add the "(ind1, ind2, ...)"
167  # TODO 1320: since we support strings and integer, we cannot
168  # simply pass the list of indices to writer.gen_indices
169  # (since it only accepts PSyIR Nodes). Instead we convert each
170  # string to a Reference, and each integer to a Literal
171  index_list = []
172 
173  for dimension in indices:
174  if isinstance(dimension, Node):
175  index_list.append(dimension)
176  elif isinstance(dimension, int):
177  index_list.append(Literal(str(dimension),
178  INTEGER_TYPE))
179  else:
180  ref = Reference(DataSymbol(dimension, INTEGER_TYPE))
181  index_list.append(ref)
182  dims = writer.gen_indices(index_list, component)
183 
184  parenthesis = writer.array_parenthesis
185  out_list.append(component + parenthesis[0] +
186  ",".join(dims) +
187  parenthesis[1])
188 
189  # Combine the components in out_list to form the language string.
190  return writer.structure_character.join(out_list)
191 
192  # ------------------------------------------------------------------------
193  def __repr__(self):
194  return f"Signature({str(self)})"
195 
196  # ------------------------------------------------------------------------
197  def __hash__(self):
198  '''This returns a hash value that is independent of the instance.
199  I.e. two instances with the same signature will have the same
200  hash key.
201  '''
202  return hash(self._signature_signature)
203 
204  # ------------------------------------------------------------------------
205  def __eq__(self, other):
206  '''Required in order to use a Signature instance as a key.
207  Compares two objects (one of which might not be a Signature).'''
208  if not hasattr(other, "_signature"):
209  return False
210  return self._signature_signature == other._signature
211 
212  # ------------------------------------------------------------------------
213  def __ne__(self, other):
214  '''Required for != comparisons of Signatures with python2.
215  Compares two objects (one of which might not be a Signature).'''
216  if not hasattr(other, "_signature"):
217  return True
218  return self._signature_signature != other._signature
219 
220  # ------------------------------------------------------------------------
221  def __lt__(self, other):
222  '''Required to sort signatures. It just compares the tuples.'''
223  if not isinstance(other, Signature):
224  raise TypeError(f"'<' not supported between instances of "
225  f"'Signature' and '{type(other).__name__}'.")
226  return self._signature_signature < other._signature
227 
228  # ------------------------------------------------------------------------
229  def __le__(self, other):
230  '''Required to compare signatures. It just compares the tuples.'''
231  if not isinstance(other, Signature):
232  raise TypeError(f"'<=' not supported between instances of "
233  f"'Signature' and '{type(other).__name__}'.")
234  return self._signature_signature <= other._signature
235 
236  # ------------------------------------------------------------------------
237  def __gt__(self, other):
238  '''Required to compare signatures. It just compares the tuples.'''
239  if not isinstance(other, Signature):
240  raise TypeError(f"'>' not supported between instances of "
241  f"'Signature' and '{type(other).__name__}'.")
242  return self._signature_signature > other._signature
243 
244  # ------------------------------------------------------------------------
245  def __ge__(self, other):
246  '''Required to compare signatures. It just compares the tuples.'''
247  if not isinstance(other, Signature):
248  raise TypeError(f"'>=' not supported between instances of "
249  f"'Signature' and '{type(other).__name__}'.")
250  return self._signature_signature >= other._signature
251 
252  # ------------------------------------------------------------------------
253  @property
254  def var_name(self):
255  ''':returns: the actual variable name, i.e. the first component of
256  the signature.
257  :rtype: str
258  '''
259  return self._signature_signature[0]
260 
261 
262 # ---------- Documentation utils -------------------------------------------- #
263 # The list of module members that we wish AutoAPI to generate
264 # documentation for. (See https://psyclone-ref.readthedocs.io)
265 __all__ = ["Signature"]
def to_language(self, component_indices=None, language_writer=None)
Definition: signature.py:110