Source code for psyclone.psyir.transformations.omp_taskloop_trans

#
# 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