42 ''' This module contains the implementation of the various OpenACC Directive
67 Base mixin class for all OpenACC directive statements.
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)`
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.
81 ''' Base class for all OpenACC region directive statements.
86 Perform validation checks for any global constraints. This can only
87 be done at code-generation time.
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.
98 data_nodes = self.
walkwalk((PSyDataNode, CodeBlock))
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__}'")
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.
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`]]]
128 if self.
ancestorancestor((LFRicInvokeSchedule, GOInvokeSchedule)):
131 for call
in self.
kernelskernels():
132 for arg_str
in call.arguments.acc_args:
137 return (set(rwi.signatures_read),
138 set(rwi.signatures_written))
142 metaclass=abc.ABCMeta):
143 ''' Base class for all standalone OpenACC directive statements. '''
147 ''' Class representing a "!$ACC routine" OpenACC directive in PSyIR. '''
150 '''Generate the fortran ACC Routine Directive and any associated code.
152 :param parent: the parent Node in the Schedule to which to add our \
154 :type parent: sub-class of :py:class:`psyclone.f2pygen.BaseGen`
160 parent.add(
DirectiveGen(parent,
"acc",
"begin",
"routine",
""))
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. "!$").
167 :returns: the opening statement of this directive.
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.
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`
186 def __init__(self, children=None, parent=None):
187 super().__init__(children=children, parent=parent)
193 '''Generate the elements of the f2pygen AST for this Node in the
196 :param parent: node in the f2pygen AST to which to add node(s).
197 :type parent: :py:class:`psyclone.f2pygen.BaseGen`
199 :raises GenerationError: if no data is found to copy in.
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",
221 In-place replacement of this directive concept into language level
224 :returns: the lowered version of this node.
225 :rtype: :py:class:`psyclone.psyir.node.Node`
233 (ACCParallelDirective, ACCKernelsDirective))
239 self.
_sig_set_sig_set.update(*pdir.signatures)
244 '''Returns the beginning statement of this directive. The visitor is
245 responsible for adding the correct directive beginning (e.g. "!$").
247 :returns: the opening statement of this directive.
250 :raises GenerationError: if there are no variables to copy to \
258 "ACCEnterData directive did not find any data to copyin. "
259 "Perhaps there are no ACCParallel or ACCKernels directives "
260 "within the region?")
262 sym_list = _sig_set_to_string(self.
_sig_set_sig_set)
266 return f
"acc enter data copyin({sym_list})"
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.
275 :param parent: the node in the InvokeSchedule to which to add nodes
276 :type parent: :py:class:`psyclone.psyir.nodes.Node`
280 class ACCParallelDirective(ACCRegionDirective):
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
287 :param bool default_present: whether this directive includes the
288 'DEFAULT(PRESENT)' clause.
291 def __init__(self, default_present=True, **kwargs):
297 Generate the elements of the f2pygen AST for this Node in the Schedule.
299 :param parent: node in the f2pygen AST to which to add node(s).
300 :type parent: :py:class:`psyclone.f2pygen.BaseGen`
309 child.gen_code(parent)
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. "!$").
321 :returns: the opening statement of this directive.
330 return "acc parallel default(present)"
331 return "acc parallel"
335 :returns: the closing statement for this directive.
338 return "acc end parallel"
341 def default_present(self):
343 :returns: whether the directive includes the 'default(present)' clause.
348 @default_present.setter
349 def default_present(self, value):
351 :param bool value: whether the directive should include the
352 'default(present)' clause.
354 :raises TypeError: if the given value is not a boolean.
357 if not isinstance(value, bool):
359 f
"The ACCParallelDirective default_present property must be "
360 f
"a boolean but value '{value}' has been given.")
366 Returns a list of the names of field objects required by the Kernel
367 call(s) that are children of this directive.
369 :returns: list of names of field arguments.
374 for call
in self.
kernelskernels():
375 for arg
in call.arguments.fields:
376 if arg
not in fld_list:
383 Class managing the creation of a '!$acc loop' OpenACC directive.
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
391 :param bool gang: whether or not to add the `gang` clause to the
393 :param bool vector: whether or not to add the `vector` clause to the
395 :param kwargs: additional keyword arguments provided to the super class.
396 :type kwargs: unwrapped dict.
398 def __init__(self, collapse=None, independent=True, sequential=False,
399 gang=False, vector=False, **kwargs):
403 self.
_gang_gang = gang
405 super().__init__(**kwargs)
409 Checks whether two nodes are equal. Two ACCLoopDirective nodes are
410 equal if their collapse, independent, sequential, gang, and vector
413 :param object other: the object to check equality to.
415 :returns: whether other is equal to self.
418 is_eq = super().
__eq__(other)
422 is_eq = is_eq
and self.
gangganggang == other.gang
423 is_eq = is_eq
and self.
vectorvectorvector == other.vector
430 :returns: the number of nested loops to collapse into a single \
431 iteration space for this node.
437 def collapse(self, value):
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]
443 :raises TypeError: if the collapse value given is not an integer \
445 :raises ValueError: if the collapse integer given is not positive.
448 if value
is not None and not isinstance(value, int):
450 f
"The ACCLoopDirective collapse clause must be a positive "
451 f
"integer or None, but value '{value}' has been given.")
453 if value
is not None and value <= 0:
455 f
"The ACCLoopDirective collapse clause must be a positive "
456 f
"integer or None, but value '{value}' has been given.")
461 def independent(self):
462 ''' Returns whether the independent clause will be added to this
465 :returns: whether the independent clause will be added to this loop \
472 def sequential(self):
474 :returns: whether or not the `seq` clause is added to this loop \
483 :returns: whether or not the `gang` clause is added to this loop
487 return self.
_gang_gang
492 :returns: whether or not the `vector` clause is added to this loop
500 Returns the name of this node with (optional) control codes
501 to generate coloured output in a terminal that supports it.
503 :param bool colour: whether or not to include colour control codes.
505 :returns: description of this node, possibly coloured.
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}]"
518 Perform validation of those global constraints that can only be done
519 at code-generation time.
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.
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 "")
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.")
541 Generate the f2pygen AST entries in the Schedule for this OpenACC
544 :param parent: the parent Node in the Schedule to which to add our
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.
554 options_str = self.
begin_stringbegin_string(leading_acc=
False)
556 parent.add(
DirectiveGen(parent,
"acc",
"begin",
"loop", options_str))
559 child.gen_code(parent)
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.
566 :param bool leading_acc: whether or not to include the leading \
567 "acc loop" in the text that is returned.
569 :returns: the opening statement of this directive.
575 clauses = [
"acc",
"loop"]
583 clauses += [
"vector"]
585 clauses += [
"independent"]
587 clauses += [f
"collapse({self._collapse})"]
588 return " ".join(clauses)
592 Would return the end string for this directive but "acc loop"
593 doesn't have a closing directive.
595 :returns: empty string.
604 Class representing the !$ACC KERNELS directive in the PSyIR.
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.
615 def __init__(self, children=None, parent=None, default_present=True):
616 super().__init__(children=children, parent=parent)
621 Checks whether two nodes are equal. Two ACCKernelsDirective nodes are
622 equal if their default_present members are equal.
624 :param object other: the object to check equality to.
626 :returns: whether other is equal to self.
629 is_eq = super().
__eq__(other)
635 def default_present(self):
637 :returns: whether the "default(present)" clause is added to the \
645 Generate the f2pygen AST entries in the Schedule for this
646 OpenACC Kernels directive.
648 :param parent: the parent Node in the Schedule to which to add this \
650 :type parent: sub-class of :py:class:`psyclone.f2pygen.BaseGen`
660 child.gen_code(parent)
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. "!$").
671 :returns: the beginning statement for this directive.
675 result =
"acc kernels"
677 result +=
" default(present)"
682 Returns the ending statement for this directive. The backend is
683 responsible for adding the language-specific syntax that marks this
686 :returns: the closing statement for this directive.
690 return "acc end kernels"
695 Class representing the !$ACC DATA ... !$ACC END DATA directive
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.
708 "ACCDataDirective.gen_code should not have been called.")
711 def _validate_child(position, child):
713 Check that the supplied node is a valid child of this node at the
716 :param int position: the proposed position of this child in the list
718 :param child: the proposed child node.
719 :type child: :py:class:`psyclone.psyir.nodes.Node`
721 :returns: whether or not the proposed child and position are valid.
726 return isinstance(child, Schedule)
727 return isinstance(child, (ACCCopyClause, ACCCopyInClause,
732 :returns: the beginning of the opening statement of this directive.
739 :returns: the text for the end of this directive region.
743 return "acc end data"
745 def _update_node(self):
747 Called whenever there is a change in the PSyIR tree below this node.
749 Ensures that the various data-movement clauses are up-to-date.
754 def _update_data_movement_clauses(self):
756 Updates the data-movement clauses on this directive.
758 First removes any such clauses and then regenerates them using
759 dependence analysis to determine which variables (if any) need moving.
765 (ACCCopyInClause, ACCCopyOutClause, ACCCopyClause)):
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.
787 :param signatures: the access signature(s) that need to be synchronised \
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
800 :type if_present: Optional[bool]
803 _VALID_DIRECTIONS = (
"self",
"host",
"device")
805 def __init__(self, signatures, direction, children=None, parent=None,
807 super().__init__(children=children, parent=parent)
815 Checks whether two nodes are equal. Two ACCUpdateDirective nodes are
816 equal if their sig_set, direction and if_present members are equal.
818 :param object other: the object to check equality to.
820 :returns: whether other is equal to self.
823 is_eq = super().
__eq__(other)
833 :returns: the set of signatures to synchronise with the device.
834 :rtype: Set[:py:class:`psyclone.core.Signature`]
841 :returns: the direction of the synchronisation.
847 def if_present(self):
849 :returns: whether or not to add the 'if_present' clause.
855 def sig_set(self, signatures):
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`]
861 :raises TypeError: if signatures is not a set of access signatures.
863 if not all(isinstance(sig, Signature)
for sig
in signatures):
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)}")
872 def direction(self, direction):
874 :param str direction: the direction of the synchronisation.
876 :raises ValueError: if the direction argument is not a string with \
877 value 'self', 'host' or 'device'.
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}'.")
888 def if_present(self, if_present):
890 :param bool if_present: whether or not to add the 'if_present' \
893 :raises TypeError: if if_present is not a boolean.
895 if not isinstance(if_present, bool):
897 f
"The ACCUpdateDirective if_present argument must be a "
898 f
"boolean but got {type(if_present).__name__}")
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. "!$").
908 :returns: the opening statement of this directive.
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.")
922 condition =
"if_present " if self.
_if_present_if_present
else ""
923 sym_list = _sig_set_to_string(self.
_sig_set_sig_set)
925 return f
"acc update {condition}{self._direction}({sym_list})"
928 def _sig_set_to_string(sig_set):
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.
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.
940 names = {s[:i+1].to_language()
for s
in sig_set
for i
in range(len(s))}
941 return ",".join(sorted(names))
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.
954 :returns: the opening string statement of this directive.
962 :returns: the ending string statement of this directive.
966 return "acc end atomic"
970 ''' Check if a given statement is a valid OpenACC atomic expression.
972 :param stmt: a node to be validated.
973 :type stmt: :py:class:`psyclone.psyir.nodes.Node`
975 :returns: whether a given statement is compliant with the OpenACC
980 if not isinstance(stmt, Assignment):
985 if not isinstance(stmt.lhs.datatype, ScalarType):
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):
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):
1009 if stmt.lhs
not in stmt.rhs.children:
1015 ''' Perform validation of those global constraints that can only be
1016 done at code-generation time.
1018 :raises GenerationError: if the ACCAtomicDirective associated
1019 statement does not conform to a valid OpenACC atomic operation.
1023 f
"Atomic directives must always have one and only one"
1024 f
" associated statement, but found '{self.debug_string()}'")
1028 f
"Statement '{self.children[0].debug_string()}' is not a "
1029 f
"valid OpenACC Atomic statement.")
1033 __all__ = [
"ACCRegionDirective",
"ACCEnterDataDirective",
1034 "ACCParallelDirective",
"ACCLoopDirective",
"ACCKernelsDirective",
1035 "ACCDataDirective",
"ACCUpdateDirective",
"ACCStandaloneDirective",
1036 "ACCDirective",
"ACCRoutineDirective",
"ACCAtomicDirective"]
def is_valid_atomic_statement(stmt)
def validate_global_constraints(self)
def _update_data_movement_clauses(self)
def lower_to_language_level(self)
def gen_code(self, parent)
def data_on_device(self, parent)
def gen_code(self, parent)
def default_present(self)
def collapse(self, value)
def validate_global_constraints(self)
def node_str(self, colour=True)
def begin_string(self, leading_acc=True)
def gen_code(self, parent)
def __init__(self, default_present=True, **kwargs)
def default_present(self, value)
def gen_code(self, parent)
def default_present(self)
def validate_global_constraints(self)
def gen_code(self, parent)
def sig_set(self, signatures)
def direction(self, direction)
def if_present(self, if_present)
def create_data_movement_deep_copy_refs(self)
def gen_post_region_code(self, parent)
def children(self, my_children)
def addchild(self, child, index=None)
def lower_to_language_level(self)
def coloured_name(self, colour=True)
def walk(self, my_type, stop_type=None, depth=None)
def validate_global_constraints(self)
def ancestor(self, my_type, excluding=None, include_self=False, limit=None, shared_with=None)