Reference Guide  2.5.0
omp_loop_trans.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2017-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: S. Siso and N. Nobre, STFC Daresbury Lab
35 
36 ''' Transformation to insert OpenMP directives to parallelise PSyIR Loops. '''
37 
38 from psyclone.configuration import Config
39 from psyclone.psyir.nodes import (
40  Routine, OMPDoDirective, OMPLoopDirective, OMPParallelDoDirective,
41  OMPTeamsDistributeParallelDoDirective, OMPScheduleClause)
42 from psyclone.psyir.symbols import DataSymbol, INTEGER_TYPE
44  ParallelLoopTrans
45 
46 MAP_STR_TO_LOOP_DIRECTIVES = {
47  "do": OMPDoDirective,
48  "paralleldo": OMPParallelDoDirective,
49  "teamsdistributeparalleldo": OMPTeamsDistributeParallelDoDirective,
50  "loop": OMPLoopDirective
51 }
52 VALID_OMP_DIRECTIVES = list(MAP_STR_TO_LOOP_DIRECTIVES.keys())
53 
54 
56  '''
57  Adds an OpenMP directive to parallelise this loop. It can insert different
58  directives such as "omp do/for", "omp parallel do/for", "omp teams
59  distribute parallel do/for" or "omp loop" depending on the provided
60  parameters.
61  The OpenMP schedule to use can also be specified, but this will be ignored
62  in case of the "omp loop" (as the 'schedule' clause is not valid for this
63  specific directive). The configuration-defined 'reprod' parameter
64  also specifies whether a manual reproducible reproduction is to be used.
65  Note, reproducible in this case means obtaining the same results with the
66  same number of OpenMP threads, not for different numbers of OpenMP threads.
67 
68  :param str omp_schedule: the OpenMP schedule to use. Defaults to 'auto'.
69  :param str omp_directive: choose which OpenMP loop directive to use. \
70  Defaults to "omp do"
71 
72  For example:
73 
74  >>> from psyclone.psyir.frontend.fortran import FortranReader
75  >>> from psyclone.psyir.backend.fortran import FortranWriter
76  >>> from psyclone.psyir.nodes import Loop
77  >>> from psyclone.transformations import OMPLoopTrans, OMPParallelTrans
78  >>>
79  >>> psyir = FortranReader().psyir_from_source("""
80  ... subroutine my_subroutine()
81  ... integer, dimension(10, 10) :: A
82  ... integer :: i
83  ... integer :: j
84  ... do i = 1, 10
85  ... do j = 1, 10
86  ... A(i, j) = 0
87  ... end do
88  ... end do
89  ... end subroutine
90  ... """)
91  >>> loop = psyir.walk(Loop)[0]
92  >>> omplooptrans1 = OMPLoopTrans(omp_schedule="dynamic",
93  ... omp_directive="paralleldo")
94  >>> omplooptrans1.apply(loop)
95  >>> print(FortranWriter()(psyir))
96  subroutine my_subroutine()
97  integer, dimension(10,10) :: a
98  integer :: i
99  integer :: j
100  <BLANKLINE>
101  !$omp parallel do default(shared), private(i,j), schedule(dynamic)
102  do i = 1, 10, 1
103  do j = 1, 10, 1
104  a(i,j) = 0
105  enddo
106  enddo
107  !$omp end parallel do
108  <BLANKLINE>
109  end subroutine my_subroutine
110  <BLANKLINE>
111 
112  '''
113  def __init__(self, omp_directive="do", omp_schedule="auto"):
114  super().__init__()
115  # Whether or not to generate code for (run-to-run on n threads)
116  # reproducible OpenMP reductions. This setting can be overridden
117  # via the `reprod` argument to the apply() method.
118  self._reprod_reprod = Config.get().reproducible_reductions
119 
120  # Use setters to set up attributes
121  self._omp_schedule_omp_schedule = ""
122  self.omp_scheduleomp_scheduleomp_scheduleomp_schedule = omp_schedule
123 
124  self._omp_directive_omp_directive = ""
125  self.omp_directiveomp_directiveomp_directiveomp_directive = omp_directive
126 
127  def __str__(self):
128  return "Adds an OpenMP directive to parallelise the target loop"
129 
130  @property
131  def omp_directive(self):
132  '''
133  :returns: the type of OMP directive that this transformation will \
134  insert.
135  :rtype: str
136  '''
137  return self._omp_directive_omp_directive
138 
139  @omp_directive.setter
140  def omp_directive(self, value):
141  '''
142  :param str value: the type of OMP directive to add.
143 
144  :raises TypeError: if the provided value is not a valid str.
145  '''
146  if not isinstance(value, str) or value not in VALID_OMP_DIRECTIVES:
147  raise TypeError(
148  f"The {type(self).__name__}.omp_directive property must be "
149  f"a str with the value of {VALID_OMP_DIRECTIVES}"
150  f" but found a '{type(value).__name__}' with value '{value}'.")
151  self._omp_directive_omp_directive = value
152 
153  @property
154  def omp_schedule(self):
155  '''
156  :returns: the OpenMP schedule that will be specified by \
157  this transformation.
158  :rtype: str
159 
160  '''
161  return self._omp_schedule_omp_schedule
162 
163  @omp_schedule.setter
164  def omp_schedule(self, value):
165  '''
166  :param str value: Sets the OpenMP schedule value that will be \
167  specified by this transformation, unless adding an OMP Loop \
168  directive (in which case it is not applicable).
169 
170  :raises TypeError: if the provided value is not a string.
171  :raises ValueError: if the provided string is not a valid OpenMP \
172  schedule format.
173  '''
174 
175  if not isinstance(value, str):
176  raise TypeError(
177  f"The OMPLoopTrans.omp_schedule property must be a 'str'"
178  f" but found a '{type(value).__name__}'.")
179 
180  # Some schedules have an optional chunk size following a ','
181  value_parts = value.split(',')
182  if value_parts[0].lower() not in OMPScheduleClause.VALID_OMP_SCHEDULES:
183  raise ValueError(
184  f"Valid OpenMP schedules are "
185  f"{OMPScheduleClause.VALID_OMP_SCHEDULES} but got "
186  f"'{value_parts[0]}'.")
187 
188  if len(value_parts) > 1:
189  if value_parts[0] == "auto":
190  raise ValueError("Cannot specify a chunk size when using an "
191  "OpenMP schedule of 'auto'.")
192  try:
193  int(value_parts[1].strip())
194  except ValueError as err:
195  raise ValueError(f"Supplied OpenMP schedule '{value}' has an "
196  f"invalid chunk-size.") from err
197 
198  self._omp_schedule_omp_schedule = value
199 
200  def _directive(self, children, collapse=None):
201  ''' Creates the type of directive needed for this sub-class of
202  transformation.
203 
204  :param children: list of Nodes that will be the children of \
205  the created directive.
206  :type children: List[:py:class:`psyclone.psyir.nodes.Node`]
207  :param int collapse: number of nested loops to collapse or None if \
208  no collapse attribute is required.
209 
210  :returns: the new node representing the directive in the AST
211  :rtype: :py:class:`psyclone.psyir.nodes.OMPDoDirective` | \
212  :py:class:`psyclone.psyir.nodes.OMPParallelDoDirective` | \
213  :py:class:`psyclone.psyir.nodes. \
214  OMPTeamsDistributeParallelDoDirective` | \
215  :py:class:`psyclone.psyir.nodes.OMPLoopDirective`
216  '''
217  node = MAP_STR_TO_LOOP_DIRECTIVES[self._omp_directive_omp_directive](
218  children=children,
219  collapse=collapse)
220  # OMP loop does not support 'schedule' or 'reprod' attributes, so we do
221  # not attempt to set these properties for this specific directive
222  if self._omp_directive_omp_directive != "loop":
223  node.omp_schedule = self._omp_schedule_omp_schedule
224  node.reprod = self._reprod_reprod
225  return node
226 
227  def apply(self, node, options=None):
228  '''Apply the OMPLoopTrans transformation to the specified PSyIR Loop.
229 
230  :param node: the supplied node to which we will apply the \
231  OMPLoopTrans transformation
232  :type node: :py:class:`psyclone.psyir.nodes.Node`
233  :param options: a dictionary with options for transformations\
234  and validation.
235  :type options: Optional[Dict[str, Any]]
236  :param bool options["reprod"]:
237  indicating whether reproducible reductions should be used. \
238  By default the value from the config file will be used.
239 
240  '''
241  if not options:
242  options = {}
243  self._reprod_reprod = options.get("reprod",
244  Config.get().reproducible_reductions)
245 
246  if self._reprod_reprod:
247  # When reprod is True, the variables th_idx and nthreads are
248  # expected to be declared in the scope.
249  root = node.ancestor(Routine)
250 
251  symtab = root.symbol_table
252  try:
253  symtab.lookup_with_tag("omp_thread_index")
254  except KeyError:
255  symtab.new_symbol(
256  "th_idx", tag="omp_thread_index",
257  symbol_type=DataSymbol, datatype=INTEGER_TYPE)
258  try:
259  symtab.lookup_with_tag("omp_num_threads")
260  except KeyError:
261  symtab.new_symbol(
262  "nthreads", tag="omp_num_threads",
263  symbol_type=DataSymbol, datatype=INTEGER_TYPE)
264 
265  super().apply(node, options)