#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
# Authors A. B. G. Chalk, A. R. Porter, STFC Daresbury Lab
'''
This module provides the implementation of OMPTaskloopTrans
'''
from typing import Union
from psyclone.psyir.transformations.parallel_loop_trans import (
ParallelLoopTrans)
from psyclone.psyir.transformations.transformation_error import (
TransformationError)
from psyclone.psyir.nodes import (
OMPTaskloopDirective)
from psyclone.utils import transformation_documentation_wrapper
[docs]
@transformation_documentation_wrapper
class OMPTaskloopTrans(ParallelLoopTrans):
'''
Adds an OpenMP taskloop directive to a loop. Only one of grainsize or
num_tasks must be specified.
TODO: #1364 Taskloops do not yet support reduction clauses.
:param grainsize: the grainsize to use in for this transformation.
:type grainsize: int or None
:param num_tasks: the num_tasks to use for this transformation.
:type num_tasks: int or None
:param bool nogroup: whether or not to use a nogroup clause for this
transformation. Default is False.
For example:
>>> from pysclone.parse.algorithm import parse
>>> from psyclone.psyGen import PSyFactory
>>> api = "gocean"
>>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api)
>>> psy = PSyFactory(api).create(invokeInfo)
>>>
>>> from psyclone.transformations import OMPSingleTrans
>>> from psyclone.psyir.transformations import OMPParallelTrans
>>> from psyclone.transformations import OMPTaskloopTrans
>>> from psyclone.psyir.transformations import OMPTaskwaitTrans
>>> singletrans = OMPSingleTrans()
>>> paralleltrans = OMPParallelTrans()
>>> tasklooptrans = OMPTaskloopTrans()
>>> taskwaittrans = OMPTaskwaitTrans()
>>>
>>> schedule = psy.invokes.get('invoke_0').schedule
>>> # Uncomment the following line to see a text view of the schedule
>>> # print(schedule.view())
>>>
>>> # Apply the OpenMP Taskloop transformation to *every* loop
>>> # in the schedule.
>>> # This ignores loop dependencies. These can be handled
>>> # by the OMPTaskwaitTrans
>>> for child in schedule.children:
>>> tasklooptrans.apply(child)
>>> # Enclose all of these loops within a single OpenMP
>>> # SINGLE region
>>> singletrans.apply(schedule.children)
>>> # Enclose all of these loops within a single OpenMP
>>> # PARALLEL region
>>> paralleltrans.apply(schedule.children)
>>> # Ensure loop dependencies are satisfied
>>> taskwaittrans.apply(schedule.children)
>>> # Uncomment the following line to see a text view of the schedule
>>> # print(schedule.view())
'''
def __init__(self, grainsize=None, num_tasks=None, nogroup=False):
self._grainsize = None
self._num_tasks = None
self.omp_grainsize = grainsize
self.omp_num_tasks = num_tasks
self.omp_nogroup = nogroup
super().__init__()
def __str__(self):
return "Adds an 'OpenMP TASKLOOP' directive to a loop"
@property
def omp_nogroup(self):
'''
Returns whether the nogroup clause should be specified for
this transformation. By default the nogroup clause is applied.
:returns: whether the nogroup clause should be specified by
this transformation.
:rtype: bool
'''
return self._nogroup
@omp_nogroup.setter
def omp_nogroup(self, nogroup):
'''
Sets whether the nogroup clause should be specified for this
transformation.
:param bool nogroup: value to set whether the nogroup clause should be
used for this transformation.
raises TypeError: if the nogroup parameter is not a bool.
'''
if not isinstance(nogroup, bool):
raise TypeError(f"Expected nogroup to be a bool "
f"but got a {type(nogroup).__name__}")
self._nogroup = nogroup
@property
def omp_grainsize(self):
'''
Returns the grainsize that will be specified by
this transformation. By default the grainsize
clause is not applied, so grainsize is None.
:returns: The grainsize specified by this transformation.
:rtype: int or None
'''
return self._grainsize
@omp_grainsize.setter
def omp_grainsize(self, value):
'''
Sets the grainsize that will be specified by
this transformation. Checks the grainsize is
a positive integer value or None.
:param value: integer value to use in the grainsize clause.
:type value: int or None
:raises TransformationError: if value is not an int and is not None.
:raises TransformationError: if value is negative.
:raises TransformationError: if grainsize and num_tasks are \
both specified.
'''
if (not isinstance(value, int)) and (value is not None):
raise TransformationError(f"grainsize must be an integer or None, "
f"got {type(value).__name__}")
if (value is not None) and (value <= 0):
raise TransformationError(f"grainsize must be a positive "
f"integer, got {value}")
if value is not None and self.omp_num_tasks is not None:
raise TransformationError(
"The grainsize and num_tasks clauses would both "
"be specified for this Taskloop transformation")
self._grainsize = value
@property
def omp_num_tasks(self):
'''
Returns the num_tasks that will be specified
by this transformation. By default the num_tasks
clause is not applied so num_tasks is None.
:returns: The grainsize specified by this transformation.
:rtype: int or None
'''
return self._num_tasks
@omp_num_tasks.setter
def omp_num_tasks(self, value):
'''
Sets the num_tasks that will be specified by
this transformation. Checks that num_tasks is
a positive integer value or None.
:param value: integer value to use in the num_tasks clause.
:type value: int or None
:raises TransformationError: if value is not an int and is not None.
:raises TransformationError: if value is negative.
:raises TransformationError: if grainsize and num_tasks are \
both specified.
'''
if (not isinstance(value, int)) and (value is not None):
raise TransformationError(f"num_tasks must be an integer or None,"
f" got {type(value).__name__}")
if (value is not None) and (value <= 0):
raise TransformationError(f"num_tasks must be a positive "
f"integer, got {value}")
if value is not None and self.omp_grainsize is not None:
raise TransformationError(
"The grainsize and num_tasks clauses would both "
"be specified for this Taskloop transformation")
self._num_tasks = value
def _directive(self, children, collapse=None):
'''
Creates the type of directive needed for this sub-class of
transformation.
:param children: list of Nodes that will be the children of
the created directive.
:type children: list of :py:class:`psyclone.psyir.nodes.Node`
:param int collapse: currently un-used but required to keep
interface the same as in base class.
:returns: the new node representing the directive in the AST.
:rtype: :py:class:`psyclone.psyir.nodes.OMPTaskloopDirective`
:raises NotImplementedError: if a collapse argument is supplied
'''
# TODO 2672: OpenMP loop functions don't support collapse
if collapse:
raise NotImplementedError(
"The COLLAPSE clause is not yet supported for "
"'!$omp taskloop' directives (#2672).")
_directive = OMPTaskloopDirective(children=children,
grainsize=self.omp_grainsize,
num_tasks=self.omp_num_tasks,
nogroup=self.omp_nogroup)
return _directive
[docs]
def apply(self, node, options=None, nogroup: Union[bool, None] = None,
**kwargs):
'''Apply the OMPTaskloopTrans transformation to the specified node in
a Schedule. This node must be a Loop since this transformation
corresponds to wrapping the generated code with directives like so:
.. code-block:: fortran
!$OMP TASKLOOP
do ...
...
end do
!$OMP END TASKLOOP
At code-generation time (when lowering is called), this node must be
within (i.e. a child of) an OpenMP SERIAL region.
If the nogroup option is True, it will cause a
nogroup clause be generated. This will override
the value supplied to the constructor, but will only apply to the
apply call to which the value is supplied.
:param node: the supplied node to which we will apply the
OMPTaskloopTrans transformation
:type node: :py:class:`psyclone.psyir.nodes.Node`
:param options: a dictionary with options for transformations
and validation.
:type options: Optional[Dict[str, Any]]
:param nogroup:
indicating whether a nogroup clause should be applied to
this taskloop. Defaults to None, which means to use the
option defined by the transformations omp_nogroup member.
'''
current_nogroup = self.omp_nogroup
# If nogroup is specified it overrides that supplied to the
# constructor of the Transformation, but will be reset at the
# end of this function
# TODO #2668: Remove options
if not options:
if nogroup is not None:
self.omp_nogroup = nogroup
else:
self.omp_nogroup = options.get("nogroup", current_nogroup)
try:
super().apply(node, options, nogroup=self.omp_nogroup, **kwargs)
finally:
# Reset the nogroup value to the original value
self.omp_nogroup = current_nogroup