36 ''' Transformation to insert OpenMP directives to parallelise PSyIR Loops. '''
40 Routine, OMPDoDirective, OMPLoopDirective, OMPParallelDoDirective,
41 OMPTeamsDistributeParallelDoDirective, OMPScheduleClause)
46 MAP_STR_TO_LOOP_DIRECTIVES = {
48 "paralleldo": OMPParallelDoDirective,
49 "teamsdistributeparalleldo": OMPTeamsDistributeParallelDoDirective,
50 "loop": OMPLoopDirective
52 VALID_OMP_DIRECTIVES = list(MAP_STR_TO_LOOP_DIRECTIVES.keys())
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
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.
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. \
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
79 >>> psyir = FortranReader().psyir_from_source("""
80 ... subroutine my_subroutine()
81 ... integer, dimension(10, 10) :: A
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
101 !$omp parallel do default(shared), private(i,j), schedule(dynamic)
107 !$omp end parallel do
109 end subroutine my_subroutine
113 def __init__(self, omp_directive="do", omp_schedule="auto"):
118 self.
_reprod_reprod = Config.get().reproducible_reductions
128 return "Adds an OpenMP directive to parallelise the target loop"
131 def omp_directive(self):
133 :returns: the type of OMP directive that this transformation will \
139 @omp_directive.setter
140 def omp_directive(self, value):
142 :param str value: the type of OMP directive to add.
144 :raises TypeError: if the provided value is not a valid str.
146 if not isinstance(value, str)
or value
not in VALID_OMP_DIRECTIVES:
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}'.")
154 def omp_schedule(self):
156 :returns: the OpenMP schedule that will be specified by \
164 def omp_schedule(self, value):
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).
170 :raises TypeError: if the provided value is not a string.
171 :raises ValueError: if the provided string is not a valid OpenMP \
175 if not isinstance(value, str):
177 f
"The OMPLoopTrans.omp_schedule property must be a 'str'"
178 f
" but found a '{type(value).__name__}'.")
181 value_parts = value.split(
',')
182 if value_parts[0].lower()
not in OMPScheduleClause.VALID_OMP_SCHEDULES:
184 f
"Valid OpenMP schedules are "
185 f
"{OMPScheduleClause.VALID_OMP_SCHEDULES} but got "
186 f
"'{value_parts[0]}'.")
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'.")
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
200 def _directive(self, children, collapse=None):
201 ''' Creates the type of directive needed for this sub-class of
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.
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`
217 node = MAP_STR_TO_LOOP_DIRECTIVES[self.
_omp_directive_omp_directive](
224 node.reprod = self.
_reprod_reprod
227 def apply(self, node, options=None):
228 '''Apply the OMPLoopTrans transformation to the specified PSyIR Loop.
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\
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.
243 self.
_reprod_reprod = options.get(
"reprod",
244 Config.get().reproducible_reductions)
249 root = node.ancestor(Routine)
251 symtab = root.symbol_table
253 symtab.lookup_with_tag(
"omp_thread_index")
256 "th_idx", tag=
"omp_thread_index",
257 symbol_type=DataSymbol, datatype=INTEGER_TYPE)
259 symtab.lookup_with_tag(
"omp_num_threads")
262 "nthreads", tag=
"omp_num_threads",
263 symbol_type=DataSymbol, datatype=INTEGER_TYPE)
265 super().
apply(node, options)