Reference Guide  2.5.0
acc_directives.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2021-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 R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab
35 # I. Kavcic, Met Office
36 # C.M. Maynard, Met Office / University of Reading
37 # J. Henrichs, Bureau of Meteorology
38 # Modified A. B. G. Chalk, STFC Daresbury Lab
39 # Modified J. G. Wallwork, Met Office
40 # -----------------------------------------------------------------------------
41 
42 ''' This module contains the implementation of the various OpenACC Directive
43 nodes.'''
44 
45 import abc
46 
47 from psyclone.core import Signature
48 from psyclone.f2pygen import DirectiveGen, CommentGen
49 from psyclone.errors import GenerationError, InternalError
50 from psyclone.psyir.nodes.acc_clauses import (ACCCopyClause, ACCCopyInClause,
51  ACCCopyOutClause)
52 from psyclone.psyir.nodes.assignment import Assignment
53 from psyclone.psyir.nodes.codeblock import CodeBlock
54 from psyclone.psyir.nodes.directive import (StandaloneDirective,
55  RegionDirective)
56 from psyclone.psyir.nodes.intrinsic_call import IntrinsicCall
57 from psyclone.psyir.nodes.psy_data_node import PSyDataNode
58 from psyclone.psyir.nodes.routine import Routine
59 from psyclone.psyir.nodes.schedule import Schedule
60 from psyclone.psyir.nodes.operation import BinaryOperation
61 from psyclone.psyir.symbols import ScalarType
62 
63 
64 class ACCDirective(metaclass=abc.ABCMeta):
65  # pylint: disable=too-few-public-methods
66  '''
67  Base mixin class for all OpenACC directive statements.
68 
69  This class is useful to provide a unique common ancestor to all the
70  OpenACC directives, for instance when traversing the tree with
71  `node.walk(ACCDirective)`
72 
73  Note that classes inheriting from it must place the ACCDirective in
74  front of the other Directive node sub-class, so that the Python
75  MRO gives preference to this class's attributes.
76  '''
77  _PREFIX = "ACC"
78 
79 
80 class ACCRegionDirective(ACCDirective, RegionDirective, metaclass=abc.ABCMeta):
81  ''' Base class for all OpenACC region directive statements.
82 
83  '''
85  '''
86  Perform validation checks for any global constraints. This can only
87  be done at code-generation time.
88 
89  :raises GenerationError: if this ACCRegionDirective encloses any form \
90  of PSyData node since calls to PSyData routines within OpenACC \
91  regions are not supported.
92 
93  '''
94  # We need to make sure to call the right constructor here:
95  # pylint: disable=bad-super-call
96  super(RegionDirective, self).validate_global_constraints()
97 
98  data_nodes = self.walkwalk((PSyDataNode, CodeBlock))
99  if data_nodes:
100  raise GenerationError(
101  f"Cannot include CodeBlocks or calls to PSyData routines "
102  f"within OpenACC regions but found "
103  f"{[type(node).__name__ for node in data_nodes]} within a "
104  f"region enclosed by an '{type(self).__name__}'")
105 
106  @property
107  def signatures(self):
108  '''
109  Returns a 1-tuple or a 2-tuple of sets depending on the working API.
110  If a 1-tuple, the set includes both input and output signatures
111  (whether to arrays or objects) required by the Kernel call(s) that are
112  children of this directive. If a 2-tuple, the first entry is the set of
113  input signatures and the second entry is the set of output signatures.
114  The set(s) describe the quantities that must be available on the remote
115  device (probably a GPU) before the parallel region can be begun.
116 
117  :returns: 1-tuple or 2-tuple of input and output sets of variable names
118  :rtype: Union[Tuple[Set[:py:class:`psyclone.core.Signature`]], \
119  Tuple[Set[:py:class:`psyclone.core.Signature`], \
120  Set[:py:class:`psyclone.core.Signature`]]]
121  '''
122 
123  # pylint: disable=import-outside-toplevel
124  from psyclone.domain.lfric import LFRicInvokeSchedule
125  from psyclone.gocean1p0 import GOInvokeSchedule
126  from psyclone.psyir.tools.call_tree_utils import CallTreeUtils
127 
128  if self.ancestorancestor((LFRicInvokeSchedule, GOInvokeSchedule)):
129  # Look-up the kernels that are children of this node
130  sig_set = set()
131  for call in self.kernelskernels():
132  for arg_str in call.arguments.acc_args:
133  sig_set.add(Signature(arg_str))
134  return (sig_set, )
135 
136  rwi = CallTreeUtils().get_in_out_parameters(self.childrenchildrenchildren)
137  return (set(rwi.signatures_read),
138  set(rwi.signatures_written))
139 
140 
142  metaclass=abc.ABCMeta):
143  ''' Base class for all standalone OpenACC directive statements. '''
144 
145 
147  ''' Class representing a "!$ACC routine" OpenACC directive in PSyIR. '''
148 
149  def gen_code(self, parent):
150  '''Generate the fortran ACC Routine Directive and any associated code.
151 
152  :param parent: the parent Node in the Schedule to which to add our \
153  content.
154  :type parent: sub-class of :py:class:`psyclone.f2pygen.BaseGen`
155  '''
156  # Check the constraints are correct
157  self.validate_global_constraintsvalidate_global_constraints()
158 
159  # Generate the code for this Directive
160  parent.add(DirectiveGen(parent, "acc", "begin", "routine", ""))
161 
162  def begin_string(self):
163  '''Returns the beginning statement of this directive, i.e.
164  "acc routine". The visitor is responsible for adding the
165  correct directive beginning (e.g. "!$").
166 
167  :returns: the opening statement of this directive.
168  :rtype: str
169 
170  '''
171  return "acc routine"
172 
173 
175  '''
176  Class representing a "!$ACC enter data" OpenACC directive in
177  an InvokeSchedule. Must be sub-classed for a particular API because the way
178  in which fields are marked as being on the remote device is API-dependent.
179 
180  :param children: list of nodes which the directive should have as children.
181  :type children: List[:py:class:`psyclone.psyir.nodes.Node`]
182  :param parent: the node in the InvokeSchedule to which to add this \
183  directive as a child.
184  :type parent: :py:class:`psyclone.psyir.nodes.Node`
185  '''
186  def __init__(self, children=None, parent=None):
187  super().__init__(children=children, parent=parent)
188  self._acc_dirs_acc_dirs = None # List of parallel directives
189 
190  self._sig_set_sig_set = set()
191 
192  def gen_code(self, parent):
193  '''Generate the elements of the f2pygen AST for this Node in the
194  Schedule.
195 
196  :param parent: node in the f2pygen AST to which to add node(s).
197  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
198 
199  :raises GenerationError: if no data is found to copy in.
200 
201  '''
202  self.validate_global_constraintsvalidate_global_constraints()
203  self.lower_to_language_levellower_to_language_levellower_to_language_level()
204  # Leverage begin_string() to raise an exception if there are no
205  # variables to copyin but discard the generated string since it is
206  # incompatible with class DirectiveGen() we are using below.
207  self.begin_stringbegin_string()
208 
209  # Add the enter data directive.
210  sym_list = _sig_set_to_string(self._sig_set_sig_set)
211  copy_in_str = f"copyin({sym_list})"
212  parent.add(DirectiveGen(parent, "acc", "begin", "enter data",
213  copy_in_str))
214  # Call an API-specific subclass of this class in case
215  # additional declarations are required.
216  self.data_on_devicedata_on_device(parent)
217  parent.add(CommentGen(parent, ""))
218 
220  '''
221  In-place replacement of this directive concept into language level
222  PSyIR constructs.
223 
224  :returns: the lowered version of this node.
225  :rtype: :py:class:`psyclone.psyir.node.Node`
226 
227  '''
228  # We must generate a list of all of the fields accessed within OpenACC
229  # compute constructs (i.e. OpenACC parallel and kernels directives)
230  # 1. Find all parallel and kernels directives. We store this list
231  # for later use in any sub-class.
232  self._acc_dirs_acc_dirs = self.ancestorancestor(Routine).walk(
233  (ACCParallelDirective, ACCKernelsDirective))
234  # 2. For each directive, add the fields used by the kernels it
235  # contains (as given by signatures) and add it to our set.
236  # TODO GOcean grid properties are duplicated in this set under
237  # different names (the OpenACC deep copy support should spot this).
238  for pdir in self._acc_dirs_acc_dirs:
239  self._sig_set_sig_set.update(*pdir.signatures)
240 
241  return super().lower_to_language_level()
242 
243  def begin_string(self):
244  '''Returns the beginning statement of this directive. The visitor is
245  responsible for adding the correct directive beginning (e.g. "!$").
246 
247  :returns: the opening statement of this directive.
248  :rtype: str
249 
250  :raises GenerationError: if there are no variables to copy to \
251  the device.
252  '''
253  if not self._sig_set_sig_set:
254  # There should be at least one variable to copyin.
255  # TODO #1872: this directive needs reimplementing using the Clause
256  # class and proper lowering.
257  raise GenerationError(
258  "ACCEnterData directive did not find any data to copyin. "
259  "Perhaps there are no ACCParallel or ACCKernels directives "
260  "within the region?")
261 
262  sym_list = _sig_set_to_string(self._sig_set_sig_set)
263 
264  # Variables need lexicographic sorting since sets guarantee no ordering
265  # and members of composite variables must appear later in deep copies.
266  return f"acc enter data copyin({sym_list})"
267 
268  def data_on_device(self, parent):
269  '''
270  Adds nodes into an InvokeSchedule to flag that the data required by the
271  kernels in the data region is now on the device. The generic
272  implementation doesn't add any node but this can be redefined in the
273  APIs if any infrastructure call is needed.
274 
275  :param parent: the node in the InvokeSchedule to which to add nodes
276  :type parent: :py:class:`psyclone.psyir.nodes.Node`
277  '''
278 
279 
280 class ACCParallelDirective(ACCRegionDirective):
281  '''
282  Class representing the !$ACC PARALLEL directive of OpenACC
283  in the PSyIR. By default it includes the 'DEFAULT(PRESENT)' clause which
284  means this node must either come after an EnterDataDirective or within
285  a DataDirective.
286 
287  :param bool default_present: whether this directive includes the
288  'DEFAULT(PRESENT)' clause.
289 
290  '''
291  def __init__(self, default_present=True, **kwargs):
292  super().__init__(**kwargs)
293  self.default_presentdefault_presentdefault_presentdefault_present = default_present
294 
295  def gen_code(self, parent):
296  '''
297  Generate the elements of the f2pygen AST for this Node in the Schedule.
298 
299  :param parent: node in the f2pygen AST to which to add node(s).
300  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
301 
302  '''
303  self.validate_global_constraintsvalidate_global_constraintsvalidate_global_constraints()
304 
305  parent.add(DirectiveGen(parent, "acc", "begin",
306  *self.begin_stringbegin_string().split()[1:]))
307 
308  for child in self.childrenchildrenchildren:
309  child.gen_code(parent)
310 
311  parent.add(DirectiveGen(parent, *self.end_stringend_string().split()))
312 
313  self.gen_post_region_codegen_post_region_code(parent)
314 
315  def begin_string(self):
316  '''
317  Returns the beginning statement of this directive, i.e.
318  "acc parallel" plus any qualifiers. The backend is responsible for
319  adding the correct characters to mark this as a directive (e.g. "!$").
320 
321  :returns: the opening statement of this directive.
322  :rtype: str
323 
324  '''
325  if self._default_present_default_present:
326  # "default(present)" means that the compiler is to assume that
327  # all data required by the parallel region is already present
328  # on the device. If we've made a mistake and it isn't present
329  # then we'll get a run-time error.
330  return "acc parallel default(present)"
331  return "acc parallel"
332 
333  def end_string(self):
334  '''
335  :returns: the closing statement for this directive.
336  :rtype: str
337  '''
338  return "acc end parallel"
339 
340  @property
341  def default_present(self):
342  '''
343  :returns: whether the directive includes the 'default(present)' clause.
344  :rtype: bool
345  '''
346  return self._default_present_default_present
347 
348  @default_present.setter
349  def default_present(self, value):
350  '''
351  :param bool value: whether the directive should include the
352  'default(present)' clause.
353 
354  :raises TypeError: if the given value is not a boolean.
355 
356  '''
357  if not isinstance(value, bool):
358  raise TypeError(
359  f"The ACCParallelDirective default_present property must be "
360  f"a boolean but value '{value}' has been given.")
361  self._default_present_default_present = value
362 
363  @property
364  def fields(self):
365  '''
366  Returns a list of the names of field objects required by the Kernel
367  call(s) that are children of this directive.
368 
369  :returns: list of names of field arguments.
370  :rtype: List[str]
371  '''
372  # Look-up the kernels that are children of this node
373  fld_list = []
374  for call in self.kernelskernels():
375  for arg in call.arguments.fields:
376  if arg not in fld_list:
377  fld_list.append(arg)
378  return fld_list
379 
380 
382  '''
383  Class managing the creation of a '!$acc loop' OpenACC directive.
384 
385  :param int collapse: Number of nested loops to collapse into a single
386  iteration space or None.
387  :param bool independent: Whether or not to add the `independent` clause
388  to the loop directive.
389  :param bool sequential: whether or not to add the `seq` clause to the
390  loop directive.
391  :param bool gang: whether or not to add the `gang` clause to the
392  loop directive.
393  :param bool vector: whether or not to add the `vector` clause to the
394  loop directive.
395  :param kwargs: additional keyword arguments provided to the super class.
396  :type kwargs: unwrapped dict.
397  '''
398  def __init__(self, collapse=None, independent=True, sequential=False,
399  gang=False, vector=False, **kwargs):
400  self.collapsecollapsecollapsecollapse = collapse
401  self._independent_independent = independent
402  self._sequential_sequential = sequential
403  self._gang_gang = gang
404  self._vector_vector = vector
405  super().__init__(**kwargs)
406 
407  def __eq__(self, other):
408  '''
409  Checks whether two nodes are equal. Two ACCLoopDirective nodes are
410  equal if their collapse, independent, sequential, gang, and vector
411  members are equal.
412 
413  :param object other: the object to check equality to.
414 
415  :returns: whether other is equal to self.
416  :rtype: bool
417  '''
418  is_eq = super().__eq__(other)
419  is_eq = is_eq and self.collapsecollapsecollapsecollapse == other.collapse
420  is_eq = is_eq and self.independentindependentindependent == other.independent
421  is_eq = is_eq and self.sequentialsequentialsequential == other.sequential
422  is_eq = is_eq and self.gangganggang == other.gang
423  is_eq = is_eq and self.vectorvectorvector == other.vector
424 
425  return is_eq
426 
427  @property
428  def collapse(self):
429  '''
430  :returns: the number of nested loops to collapse into a single \
431  iteration space for this node.
432  :rtype: int or None
433  '''
434  return self._collapse_collapse
435 
436  @collapse.setter
437  def collapse(self, value):
438  '''
439  :param value: optional number of nested loop to collapse into a \
440  single iteration space to parallelise. Defaults to None.
441  :type value: Optional[int]
442 
443  :raises TypeError: if the collapse value given is not an integer \
444  or NoneType.
445  :raises ValueError: if the collapse integer given is not positive.
446 
447  '''
448  if value is not None and not isinstance(value, int):
449  raise TypeError(
450  f"The ACCLoopDirective collapse clause must be a positive "
451  f"integer or None, but value '{value}' has been given.")
452 
453  if value is not None and value <= 0:
454  raise ValueError(
455  f"The ACCLoopDirective collapse clause must be a positive "
456  f"integer or None, but value '{value}' has been given.")
457 
458  self._collapse_collapse = value
459 
460  @property
461  def independent(self):
462  ''' Returns whether the independent clause will be added to this
463  loop directive.
464 
465  :returns: whether the independent clause will be added to this loop \
466  directive.
467  :rtype: bool
468  '''
469  return self._independent_independent
470 
471  @property
472  def sequential(self):
473  '''
474  :returns: whether or not the `seq` clause is added to this loop \
475  directive.
476  :rtype: bool
477  '''
478  return self._sequential_sequential
479 
480  @property
481  def gang(self):
482  '''
483  :returns: whether or not the `gang` clause is added to this loop
484  directive.
485  :rtype: bool
486  '''
487  return self._gang_gang
488 
489  @property
490  def vector(self):
491  '''
492  :returns: whether or not the `vector` clause is added to this loop
493  directive.
494  :rtype: bool
495  '''
496  return self._vector_vector
497 
498  def node_str(self, colour=True):
499  '''
500  Returns the name of this node with (optional) control codes
501  to generate coloured output in a terminal that supports it.
502 
503  :param bool colour: whether or not to include colour control codes.
504 
505  :returns: description of this node, possibly coloured.
506  :rtype: str
507  '''
508  text = self.coloured_namecoloured_name(colour)
509  text += f"[sequential={self._sequential},"
510  text += f"gang={self._gang},"
511  text += f"vector={self._vector},"
512  text += f"collapse={self._collapse},"
513  text += f"independent={self._independent}]"
514  return text
515 
517  '''
518  Perform validation of those global constraints that can only be done
519  at code-generation time.
520 
521  :raises GenerationError: if this ACCLoopDirective is not enclosed
522  within some OpenACC parallel or kernels region and is not in a
523  Routine that has been marked up with an 'ACC Routine' directive.
524  '''
525  parent_routine = self.ancestorancestor(Routine)
526  if not (self.ancestorancestor((ACCParallelDirective, ACCKernelsDirective),
527  limit=parent_routine) or
528  (parent_routine and parent_routine.walk(ACCRoutineDirective))):
529  location = (f"in routine '{parent_routine.name}' " if
530  parent_routine else "")
531  raise GenerationError(
532  f"ACCLoopDirective {location}must either have an "
533  f"ACCParallelDirective or ACCKernelsDirective as an ancestor "
534  f"in the Schedule or the routine must contain an "
535  f"ACCRoutineDirective.")
536 
538 
539  def gen_code(self, parent):
540  '''
541  Generate the f2pygen AST entries in the Schedule for this OpenACC
542  loop directive.
543 
544  :param parent: the parent Node in the Schedule to which to add our
545  content.
546  :type parent: sub-class of :py:class:`psyclone.f2pygen.BaseGen`
547  :raises GenerationError: if this "!$acc loop" is not enclosed within \
548  an ACC Parallel region.
549  '''
551 
552  # Add any clauses to the directive. We use self.begin_string() to avoid
553  # code duplication.
554  options_str = self.begin_stringbegin_string(leading_acc=False)
555 
556  parent.add(DirectiveGen(parent, "acc", "begin", "loop", options_str))
557 
558  for child in self.childrenchildrenchildren:
559  child.gen_code(parent)
560 
561  def begin_string(self, leading_acc=True):
562  ''' Returns the opening statement of this directive, i.e.
563  "acc loop" plus any qualifiers. If `leading_acc` is False then
564  the leading "acc loop" text is not included.
565 
566  :param bool leading_acc: whether or not to include the leading \
567  "acc loop" in the text that is returned.
568 
569  :returns: the opening statement of this directive.
570  :rtype: str
571 
572  '''
573  clauses = []
574  if leading_acc:
575  clauses = ["acc", "loop"]
576 
577  if self._sequential_sequential:
578  clauses += ["seq"]
579  else:
580  if self._gang_gang:
581  clauses += ["gang"]
582  if self._vector_vector:
583  clauses += ["vector"]
584  if self._independent_independent:
585  clauses += ["independent"]
586  if self._collapse_collapse:
587  clauses += [f"collapse({self._collapse})"]
588  return " ".join(clauses)
589 
590  def end_string(self):
591  '''
592  Would return the end string for this directive but "acc loop"
593  doesn't have a closing directive.
594 
595  :returns: empty string.
596  :rtype: str
597 
598  '''
599  return ""
600 
601 
603  '''
604  Class representing the !$ACC KERNELS directive in the PSyIR.
605 
606  :param children: the PSyIR nodes to be enclosed in the Kernels region \
607  and which are therefore children of this node.
608  :type children: List[:py:class:`psyclone.psyir.nodes.Node`]
609  :param parent: the parent of this node in the PSyIR.
610  :type parent: sub-class of :py:class:`psyclone.psyir.nodes.Node`
611  :param bool default_present: whether or not to add the "default(present)" \
612  clause to the kernels directive.
613 
614  '''
615  def __init__(self, children=None, parent=None, default_present=True):
616  super().__init__(children=children, parent=parent)
617  self._default_present_default_present = default_present
618 
619  def __eq__(self, other):
620  '''
621  Checks whether two nodes are equal. Two ACCKernelsDirective nodes are
622  equal if their default_present members are equal.
623 
624  :param object other: the object to check equality to.
625 
626  :returns: whether other is equal to self.
627  :rtype: bool
628  '''
629  is_eq = super().__eq__(other)
630  is_eq = is_eq and self.default_presentdefault_presentdefault_present == other.default_present
631 
632  return is_eq
633 
634  @property
635  def default_present(self):
636  '''
637  :returns: whether the "default(present)" clause is added to the \
638  kernels directive.
639  :rtype: bool
640  '''
641  return self._default_present_default_present
642 
643  def gen_code(self, parent):
644  '''
645  Generate the f2pygen AST entries in the Schedule for this
646  OpenACC Kernels directive.
647 
648  :param parent: the parent Node in the Schedule to which to add this \
649  content.
650  :type parent: sub-class of :py:class:`psyclone.f2pygen.BaseGen`
651 
652  '''
653  self.validate_global_constraintsvalidate_global_constraintsvalidate_global_constraints()
654 
655  # We re-use the 'begin_string' method but must skip the leading 'acc'
656  # that it includes.
657  parent.add(DirectiveGen(parent, "acc", "begin",
658  *self.begin_stringbegin_string().split()[1:]))
659  for child in self.childrenchildrenchildren:
660  child.gen_code(parent)
661 
662  parent.add(DirectiveGen(parent, *self.end_stringend_string().split()))
663 
664  self.gen_post_region_codegen_post_region_code(parent)
665 
666  def begin_string(self):
667  '''Returns the beginning statement of this directive, i.e.
668  "acc kernels ...". The backend is responsible for adding the
669  correct directive beginning (e.g. "!$").
670 
671  :returns: the beginning statement for this directive.
672  :rtype: str
673 
674  '''
675  result = "acc kernels"
676  if self._default_present_default_present:
677  result += " default(present)"
678  return result
679 
680  def end_string(self):
681  '''
682  Returns the ending statement for this directive. The backend is
683  responsible for adding the language-specific syntax that marks this
684  as a directive.
685 
686  :returns: the closing statement for this directive.
687  :rtype: str
688 
689  '''
690  return "acc end kernels"
691 
692 
694  '''
695  Class representing the !$ACC DATA ... !$ACC END DATA directive
696  in the PSyIR.
697 
698  '''
699  def gen_code(self, _):
700  '''
701  :raises InternalError: the ACC data directive is currently only \
702  supported for the NEMO API and that uses the \
703  PSyIR backend to generate code.
704  fparser2 parse tree.
705 
706  '''
707  raise InternalError(
708  "ACCDataDirective.gen_code should not have been called.")
709 
710  @staticmethod
711  def _validate_child(position, child):
712  '''
713  Check that the supplied node is a valid child of this node at the
714  specified position.
715 
716  :param int position: the proposed position of this child in the list
717  of children.
718  :param child: the proposed child node.
719  :type child: :py:class:`psyclone.psyir.nodes.Node`
720 
721  :returns: whether or not the proposed child and position are valid.
722  :rtype: bool
723 
724  '''
725  if position == 0:
726  return isinstance(child, Schedule)
727  return isinstance(child, (ACCCopyClause, ACCCopyInClause,
728  ACCCopyOutClause))
729 
730  def begin_string(self):
731  '''
732  :returns: the beginning of the opening statement of this directive.
733  :rtype: str
734  '''
735  return "acc data"
736 
737  def end_string(self):
738  '''
739  :returns: the text for the end of this directive region.
740  :rtype: str
741 
742  '''
743  return "acc end data"
744 
745  def _update_node(self):
746  '''
747  Called whenever there is a change in the PSyIR tree below this node.
748 
749  Ensures that the various data-movement clauses are up-to-date.
750 
751  '''
752  self._update_data_movement_clauses_update_data_movement_clauses()
753 
754  def _update_data_movement_clauses(self):
755  '''
756  Updates the data-movement clauses on this directive.
757 
758  First removes any such clauses and then regenerates them using
759  dependence analysis to determine which variables (if any) need moving.
760 
761  '''
762  # Remove the clauses that we will update.
763  for child in self.childrenchildrenchildren[:]:
764  if isinstance(child,
765  (ACCCopyInClause, ACCCopyOutClause, ACCCopyClause)):
766  self.childrenchildrenchildren.remove(child)
767 
768  # Use dependence analysis to identify the variables that are read,
769  # written and read+written within the tree below this node.
770  reads, writes, readwrites = self.create_data_movement_deep_copy_refscreate_data_movement_deep_copy_refs()
771 
772  if reads:
773  self.addchildaddchild(ACCCopyInClause(children=list(reads.values())))
774 
775  if writes:
776  self.addchildaddchild(ACCCopyOutClause(children=list(writes.values())))
777 
778  if readwrites:
779  self.addchildaddchild(ACCCopyClause(children=list(readwrites.values())))
780 
781 
783  ''' Class representing the OpenACC update directive in the PSyIR. It has
784  a direction attribute that can be set to 'self', 'host' or 'device', the
785  set of symbols being updated and an optional if_present clause.
786 
787  :param signatures: the access signature(s) that need to be synchronised \
788  with the device.
789  :type signatures: Set[:py:class:`psyclone.core.Signature`]
790  :param str direction: the direction of the synchronisation.
791  :param children: list of nodes which the directive should have as children.
792  :type children: List[:py:class:`psyclone.psyir.nodes.Node`]
793  :param parent: the node in the InvokeSchedule to which to add this \
794  directive as a child.
795  :type parent: :py:class:`psyclone.psyir.nodes.Node`
796  :param if_present: whether or not to include the 'if_present'
797  clause on the update directive (this instructs the
798  directive to silently ignore any variables that are not
799  on the device).
800  :type if_present: Optional[bool]
801  '''
802 
803  _VALID_DIRECTIONS = ("self", "host", "device")
804 
805  def __init__(self, signatures, direction, children=None, parent=None,
806  if_present=True):
807  super().__init__(children=children, parent=parent)
808 
809  self.sig_setsig_setsig_setsig_set = signatures
810  self.directiondirectiondirectiondirection = direction
811  self.if_presentif_presentif_presentif_present = if_present
812 
813  def __eq__(self, other):
814  '''
815  Checks whether two nodes are equal. Two ACCUpdateDirective nodes are
816  equal if their sig_set, direction and if_present members are equal.
817 
818  :param object other: the object to check equality to.
819 
820  :returns: whether other is equal to self.
821  :rtype: bool
822  '''
823  is_eq = super().__eq__(other)
824  is_eq = is_eq and self.sig_setsig_setsig_setsig_set == other.sig_set
825  is_eq = is_eq and self.directiondirectiondirectiondirection == other.direction
826  is_eq = is_eq and self.if_presentif_presentif_presentif_present == other.if_present
827 
828  return is_eq
829 
830  @property
831  def sig_set(self):
832  '''
833  :returns: the set of signatures to synchronise with the device.
834  :rtype: Set[:py:class:`psyclone.core.Signature`]
835  '''
836  return self._sig_set_sig_set
837 
838  @property
839  def direction(self):
840  '''
841  :returns: the direction of the synchronisation.
842  :rtype: str
843  '''
844  return self._direction_direction
845 
846  @property
847  def if_present(self):
848  '''
849  :returns: whether or not to add the 'if_present' clause.
850  :rtype: bool
851  '''
852  return self._if_present_if_present
853 
854  @sig_set.setter
855  def sig_set(self, signatures):
856  '''
857  :param signatures: the access signature(s) that need to be \
858  synchronised with the device.
859  :type signatures: Set[:py:class:`psyclone.core.Signature`]
860 
861  :raises TypeError: if signatures is not a set of access signatures.
862  '''
863  if not all(isinstance(sig, Signature) for sig in signatures):
864  raise TypeError(
865  f"The ACCUpdateDirective signatures argument must be a "
866  f"set of signatures but got "
867  f"{set(type(sig).__name__ for sig in signatures)}")
868 
869  self._sig_set_sig_set = signatures
870 
871  @direction.setter
872  def direction(self, direction):
873  '''
874  :param str direction: the direction of the synchronisation.
875 
876  :raises ValueError: if the direction argument is not a string with \
877  value 'self', 'host' or 'device'.
878  '''
879  if direction not in self._VALID_DIRECTIONS_VALID_DIRECTIONS:
880  raise ValueError(
881  f"The ACCUpdateDirective direction argument must be a string "
882  f"with any of the values in '{self._VALID_DIRECTIONS}' but "
883  f"found '{direction}'.")
884 
885  self._direction_direction = direction
886 
887  @if_present.setter
888  def if_present(self, if_present):
889  '''
890  :param bool if_present: whether or not to add the 'if_present' \
891  clause.
892 
893  :raises TypeError: if if_present is not a boolean.
894  '''
895  if not isinstance(if_present, bool):
896  raise TypeError(
897  f"The ACCUpdateDirective if_present argument must be a "
898  f"boolean but got {type(if_present).__name__}")
899 
900  self._if_present_if_present = if_present
901 
902  def begin_string(self):
903  '''
904  Returns the beginning statement of this directive, i.e.
905  "acc update host(symbol)". The backend is responsible for adding the
906  correct characters to mark this as a directive (e.g. "!$").
907 
908  :returns: the opening statement of this directive.
909  :rtype: str
910 
911  '''
912  if not self._sig_set_sig_set:
913  # There should be at least one variable to update.
914  # TODO #1872: this directive needs reimplementing using the Clause
915  # class and proper lowering.
916  raise GenerationError(
917  "ACCUpdate directive did not find any data to update. "
918  "This most likely happened because a specialisation of "
919  "ACCUpdateDirective.lower_to_level_language removed all the "
920  "variables this directive was created to update.")
921 
922  condition = "if_present " if self._if_present_if_present else ""
923  sym_list = _sig_set_to_string(self._sig_set_sig_set)
924 
925  return f"acc update {condition}{self._direction}({sym_list})"
926 
927 
928 def _sig_set_to_string(sig_set):
929  '''
930  Converts the provided set of signatures into a lexically sorted
931  string of comma-separated signatures which also includes, for signatures
932  that represent variables of a derived type, the composing subsignatures.
933 
934  :param sig_set: set of signature(s) to include in the string.
935  :type sig_set: Set[:py:class:`psyclone.core.Signature`]
936  :returns: a lexically sorted string of comma-separated (sub)signatures.
937  :rtype: str
938 
939  '''
940  names = {s[:i+1].to_language() for s in sig_set for i in range(len(s))}
941  return ",".join(sorted(names))
942 
943 
945  '''
946  OpenACC directive to represent that the memory accesses in the associated
947  assignment must be performed atomically.
948  Note that the standard supports blocks with 2 assignments but this is
949  currently unsupported in the PSyIR.
950 
951  '''
952  def begin_string(self):
953  '''
954  :returns: the opening string statement of this directive.
955  :rtype: str
956 
957  '''
958  return "acc atomic"
959 
960  def end_string(self):
961  '''
962  :returns: the ending string statement of this directive.
963  :rtype: str
964 
965  '''
966  return "acc end atomic"
967 
968  @staticmethod
970  ''' Check if a given statement is a valid OpenACC atomic expression.
971 
972  :param stmt: a node to be validated.
973  :type stmt: :py:class:`psyclone.psyir.nodes.Node`
974 
975  :returns: whether a given statement is compliant with the OpenACC
976  atomic expression.
977  :rtype: bool
978 
979  '''
980  if not isinstance(stmt, Assignment):
981  return False
982 
983  # Not all rules are checked, just that:
984  # - operands are of a scalar intrinsic type
985  if not isinstance(stmt.lhs.datatype, ScalarType):
986  return False
987 
988  # - the top-level operator is one of: +, *, -, /, AND, OR, EQV, NEQV
989  if isinstance(stmt.rhs, BinaryOperation):
990  if stmt.rhs.operator not in (BinaryOperation.Operator.ADD,
991  BinaryOperation.Operator.SUB,
992  BinaryOperation.Operator.MUL,
993  BinaryOperation.Operator.DIV,
994  BinaryOperation.Operator.AND,
995  BinaryOperation.Operator.OR,
996  BinaryOperation.Operator.EQV,
997  BinaryOperation.Operator.NEQV):
998  return False
999  # - or intrinsics: MAX, MIN, IAND, IOR, or IEOR
1000  if isinstance(stmt.rhs, IntrinsicCall):
1001  if stmt.rhs.intrinsic not in (IntrinsicCall.Intrinsic.MAX,
1002  IntrinsicCall.Intrinsic.MIN,
1003  IntrinsicCall.Intrinsic.IAND,
1004  IntrinsicCall.Intrinsic.IOR,
1005  IntrinsicCall.Intrinsic.IEOR):
1006  return False
1007 
1008  # - one of the operands should be the same as the lhs
1009  if stmt.lhs not in stmt.rhs.children:
1010  return False
1011 
1012  return True
1013 
1015  ''' Perform validation of those global constraints that can only be
1016  done at code-generation time.
1017 
1018  :raises GenerationError: if the ACCAtomicDirective associated
1019  statement does not conform to a valid OpenACC atomic operation.
1020  '''
1021  if not self.childrenchildrenchildren or len(self.dir_bodydir_body.children) != 1:
1022  raise GenerationError(
1023  f"Atomic directives must always have one and only one"
1024  f" associated statement, but found '{self.debug_string()}'")
1025  stmt = self.dir_bodydir_body[0]
1026  if not self.is_valid_atomic_statementis_valid_atomic_statement(stmt):
1027  raise GenerationError(
1028  f"Statement '{self.children[0].debug_string()}' is not a "
1029  f"valid OpenACC Atomic statement.")
1030 
1031 
1032 # For automatic API documentation generation
1033 __all__ = ["ACCRegionDirective", "ACCEnterDataDirective",
1034  "ACCParallelDirective", "ACCLoopDirective", "ACCKernelsDirective",
1035  "ACCDataDirective", "ACCUpdateDirective", "ACCStandaloneDirective",
1036  "ACCDirective", "ACCRoutineDirective", "ACCAtomicDirective"]
def __init__(self, default_present=True, **kwargs)
def children(self, my_children)
Definition: node.py:935
def addchild(self, child, index=None)
Definition: node.py:909
def lower_to_language_level(self)
Definition: node.py:1443
def coloured_name(self, colour=True)
Definition: node.py:453
def walk(self, my_type, stop_type=None, depth=None)
Definition: node.py:1075
def validate_global_constraints(self)
Definition: node.py:1605
def ancestor(self, my_type, excluding=None, include_self=False, limit=None, shared_with=None)
Definition: node.py:1161