41 ''' This module contains the implementation of the various OpenMP Directive
52 UnresolvedDependencyError)
67 OMPNowaitClause, OMPNogroupClause, OMPNumTasksClause, OMPPrivateClause, \
68 OMPDefaultClause, OMPReductionClause, OMPScheduleClause, \
69 OMPFirstprivateClause, OMPDependClause
80 OMP_OPERATOR_MAPPING = {AccessType.SUM:
"+"}
85 Base mixin class for all OpenMP-related directives.
87 This class is useful to provide a unique common ancestor to all the
88 OpenMP directives, for instance when traversing the tree with
89 `node.walk(OMPDirective)`
91 Note that classes inheriting from it must place the OMPDirective in
92 front of the other Directive node sub-class, so that the Python
93 MRO gives preference to this class's attributes.
100 Base class for all OpenMP region-related directives.
103 def _get_reductions_list(self, reduction_type):
105 Returns the names of all scalars within this region that require a
106 reduction of type 'reduction_type'. Returned names will be unique.
108 TODO #514 - this only works for the PSyKAl APIs currently. It needs
109 extending/replacing with the use of the PSyIR Dependence Analysis.
111 :param reduction_type: the reduction type (e.g. AccessType.SUM) to
113 :type reduction_type: :py:class:`psyclone.core.access_type.AccessType`
115 :returns: names of scalar arguments with reduction access.
122 if Config.get().api
not in (
'gocean1.0',
'dynamo0.3'):
125 const = Config.get().api_conf().get_constants()
126 for call
in self.
kernelskernels():
127 for arg
in call.arguments.args:
128 if arg.argument_type
in const.VALID_SCALAR_NAMES:
129 if arg.descriptor.access == reduction_type:
130 if arg.name
not in result:
131 result.append(arg.name)
136 metaclass=abc.ABCMeta):
137 ''' Base class for all OpenMP-related standalone directives. '''
142 Class representing an OpenMP Declare Target directive in the PSyIR.
146 '''Generate the fortran OMP Declare Target Directive and any
149 :param parent: the parent Node in the Schedule to which to add our \
151 :type parent: sub-class of :py:class:`psyclone.f2pygen.BaseGen`
157 parent.add(
DirectiveGen(parent,
"omp",
"begin",
"declare",
"target"))
160 '''Returns the beginning statement of this directive, i.e.
161 "omp routine". The visitor is responsible for adding the
162 correct directive beginning (e.g. "!$").
164 :returns: the opening statement of this directive.
168 return "omp declare target"
172 Perform validation checks that can only be done at code-generation
175 :raises GenerationError: if this directive is not the first statement \
179 if self.
parentparent
and (
not isinstance(self.
parentparent, Routine)
or
180 self.
parentparent.children[0]
is not self):
182 f
"A OMPDeclareTargetDirective must be the first child (index "
183 f
"0) of a Routine but found one as child {self.position} of a "
184 f
"{type(self.parent).__name__}.")
191 Class representing an OpenMP TASKWAIT directive in the PSyIR.
196 Perform validation checks that can only be done at code-generation
199 :raises GenerationError: if this OMPTaskwait is not enclosed \
200 within some OpenMP parallel region.
208 if not self.
ancestorancestor(OMPParallelDirective,
209 excluding=OMPParallelDoDirective):
211 "OMPTaskwaitDirective must be inside an OMP parallel region "
212 "but could not find an ancestor OMPParallelDirective node")
217 '''Generate the fortran OMP Taskwait Directive and any associated
220 :param parent: the parent Node in the Schedule to which to add our \
222 :type parent: sub-class of :py:class:`psyclone.f2pygen.BaseGen`
228 parent.add(
DirectiveGen(parent,
"omp",
"begin",
"taskwait",
""))
232 '''Returns the beginning statement of this directive, i.e.
233 "omp taskwait". The visitor is responsible for adding the
234 correct directive beginning (e.g. "!$").
236 :returns: the opening statement of this directive.
240 return "omp taskwait"
245 Abstract class representing OpenMP serial regions, e.g.
246 OpenMP SINGLE or OpenMP Master.
250 def _valid_dependence_literals(self, lit1, lit2):
252 Compares two Nodes to check whether they are a valid dependency
253 pair. For two Nodes where at least one is a Literal, a valid
254 dependency is any pair of Literals.
256 :param lit1: the first node to compare.
257 :type lit1: :py:class:`psyclone.psyir.nodes.Node`
258 :param lit2: the second node to compare.
259 :type lit2: :py:class:`psyclone.psyir.nodes.Node`
261 :returns: whether or not these two nodes can be used as a valid
262 dependency pair in OpenMP.
275 return isinstance(lit1, Literal)
and isinstance(lit2, Literal)
277 def _valid_dependence_ranges(self, arraymixin1, arraymixin2, index):
279 Compares two ArrayMixin Nodes to check whether they are a valid
280 dependency pair on the provided index. For two Nodes where at least
281 one has a Range at this index, they must both have Ranges, and both be
282 full ranges, i.e. ":".
284 :param arraymixin1: the first node to validate.
285 :type arraymixin1: :py:class:`psyclone.psyir.nodes.ArrayMixin`
286 :param arraymixin2: the second node to validate.
287 :type arraymixin2: :py:class:`psyclone.psyir.nodes.ArrayMixin`
289 :returns: whether or not these two nodes can be used as a valid
290 dependency pair in OpenMP, based upon the provided index.
296 if (
not isinstance(arraymixin1.indices[index], Range)
or not
297 isinstance(arraymixin2.indices[index], Range)):
307 return (arraymixin1.is_full_range(index)
and
308 arraymixin2.is_full_range(index))
310 def _compute_accesses_get_start_stop_step(self, preceding_nodes, task,
313 Computes the start, stop and step values used in the _compute_accesses
314 function by searching through the preceding nodes for the last access
317 :param preceding_nodes: a list of nodes that precede the task in the
319 :type preceding_nodes: List[:py:class:`psyclone.psyir.nodes.Node`]
320 :param task: the OMPTaskDirective node being used in
322 :type task: :py:class:`psyclone.psyir.nodes.OMPTaskDirective`
323 :param symbol: the symbol used by the ref in _compute_accesses.
324 :type symbol: :py:class:`psyclone.psyir.symbols.Symbol`
326 :returns: a tuple containing the start, stop and step nodes (or None
327 if there is no value).
328 :rtype: Tuple[:py:class`psyclone.psyir.nodes.Node, None]
333 for node
in preceding_nodes:
336 if not isinstance(node, (Assignment, Loop, Call)):
341 if isinstance(node, Call)
and not isinstance(node, IntrinsicCall):
346 "Found a Call in preceding_nodes, which "
347 "is not yet supported.")
348 if isinstance(node, Assignment)
and node.lhs.symbol == symbol:
349 start = node.rhs.copy()
351 if isinstance(node, Loop)
and node.variable == symbol:
354 ancestor_loop = task.ancestor(Loop, limit=self)
356 while ancestor_loop
is not None:
357 if ancestor_loop == node:
360 ancestor_loop = ancestor_loop.ancestor(Loop, limit=self)
363 f
"Found a dependency index that "
364 f
"was updated as a Loop variable "
365 f
"that is not an ancestor Loop of "
366 f
"the task. The variable is "
367 f
"'{node.variable.name}'.")
370 start, stop, step = node.start_expr, node.stop_expr, \
373 return (start, stop, step)
375 def _compute_accesses(self, ref, preceding_nodes, task):
377 Computes the set of accesses for a Reference or BinaryOperation
378 Node, based upon the preceding_nodes and containing task.
380 The returned result is either a set of Literals, or Literals
381 and BinaryOperations.
383 If ref is a BinaryOperation, it needs to be of the following formats:
384 1. Reference ADD/SUB Literal
385 2. Literal ADD Reference
386 3. Binop(Literal MUL Literal) ADD Reference
387 4. Reference ADD/SUB Binop(Literal MUL Literal)
390 :param ref: the Reference or BinaryOperation node to compute
392 :type ref: Union[:py:class:`psyclone.psyir.nodes.Reference,
393 :py:class:`psyclone.psyir.nodes.BinaryOperation]
394 :param preceding_nodes: a list of nodes that precede the task in the
396 :type preceding_nodes: List[:py:class:`psyclone.psyir.nodes.Node`]
397 :param task: the OMPTaskDirective node containing ref as a child.
398 :type task: :py:class:`psyclone.psyir.nodes.OMPTaskDirective`
400 :raises UnresolvedDependencyError: If the ref contains an unsupported
401 BinaryOperation structure, such as
402 a non-ADD/SUB/MUL operator. Check
403 error message for more details.
404 :raises UnresolvedDependencyError: If preceding_nodes contains a Call
406 :raises UnresolvedDependencyError: If ref is a BinaryOperation and
407 neither child of ref is a Literal
409 :raises UnresolvedDependencyError: If there is a dependency between
410 ref (a BinaryOperation) and a
411 previously set constant.
412 :raises UnresolvedDependencyError: If there is a dependency between
413 ref and a Loop variable that is
414 not an ancestor of task.
415 :raises UnresolvedDependencyError: If preceding_nodes contains a
416 dependent loop with a non-Literal
419 :returns: a list of the dependency values for the input ref, or a
420 dict of the start, stop and step values.
421 :rtype: List[Union[:py:class:`psyclone.psyir.nodes.Literal`,
422 :py:class:`psyclone.psyir.nodes.BinaryOperation`]] or
423 Dict[str: py:class:`psyclone.psyir.nodes.Node`]
425 if isinstance(ref, Reference):
437 if isinstance(ref.children[0], Literal):
438 if ref.operator == BinaryOperation.Operator.ADD:
441 symbol = ref.children[1].symbol
442 binop_val = int(ref.children[0].value)
446 f
"Found a dependency index that is "
447 f
"a BinaryOperation where the "
448 f
"format is Literal OP Reference "
449 f
"with a non-ADD operand "
450 f
"which is not supported. "
451 f
"The operation found was "
452 f
"'{ref.debug_string()}'.")
453 elif isinstance(ref.children[1], Literal):
457 if ref.operator
in [BinaryOperation.Operator.ADD,
458 BinaryOperation.Operator.SUB]:
459 symbol = ref.children[0].symbol
460 binop_val = int(ref.children[1].value)
462 if ref.operator == BinaryOperation.Operator.SUB:
463 binop_val = -binop_val
466 f
"Found a dependency index that is "
467 f
"a BinaryOperation where the "
468 f
"Operator is neither ADD not SUB "
469 f
"which is not supported. "
470 f
"The operation found was "
471 f
"'{ref.debug_string()}'.")
473 elif isinstance(ref.children[0], BinaryOperation):
474 if ref.operator == BinaryOperation.Operator.ADD:
479 symbol = ref.children[1].symbol
480 binop = ref.children[0]
481 if binop.operator != BinaryOperation.Operator.MUL:
483 f
"Found a dependency index that is a "
484 f
"BinaryOperation with a child "
485 f
"BinaryOperation with a non-MUL operator "
486 f
"which is not supported. "
487 f
"The operation found was "
488 f
"'{ref.debug_string()}'.")
492 if (
not (isinstance(binop.children[0], Literal)
and
493 isinstance(binop.children[1], Literal))):
495 f
"Found a dependency index that is a "
496 f
"BinaryOperation with a child "
497 f
"BinaryOperation with a non-Literal child "
498 f
"which is not supported. "
499 f
"The operation found was "
500 f
"'{ref.debug_string()}'.")
504 binop_val = int(binop.children[1].value)
505 num_entries = int(binop.children[0].value)+1
508 f
"Found a dependency index that is "
509 f
"a BinaryOperation where the "
510 f
"format is BinaryOperator OP "
511 f
"Reference with a non-ADD operand "
512 f
"which is not supported. "
513 f
"The operation found was "
514 f
"'{ref.debug_string()}'.")
515 elif isinstance(ref.children[1], BinaryOperation):
520 if ref.operator
in [BinaryOperation.Operator.ADD,
521 BinaryOperation.Operator.SUB]:
522 symbol = ref.children[0].symbol
523 binop = ref.children[1]
524 if binop.operator != BinaryOperation.Operator.MUL:
526 f
"Found a dependency index that is a "
527 f
"BinaryOperation with a child "
528 f
"BinaryOperation with a non-MUL operator "
529 f
"which is not supported. "
530 f
"The operation found was "
531 f
"'{ref.debug_string()}'.")
534 if (
not (isinstance(binop.children[0], Literal)
and
535 isinstance(binop.children[1], Literal))):
537 f
"Found a dependency index that is a "
538 f
"BinaryOperation with an operand "
539 f
"BinaryOperation with a non-Literal operand "
540 f
"which is not supported. "
541 f
"The operation found was "
542 f
"'{ref.debug_string()}'.")
546 binop_val = int(binop.children[1].value)
547 num_entries = int(binop.children[0].value)+1
548 if ref.operator == BinaryOperation.Operator.SUB:
552 binop_val = -binop_val
553 num_entries = num_entries-1
556 f
"Found a dependency index that is "
557 f
"a BinaryOperation where the "
558 f
"format is Reference OP "
559 f
"BinaryOperation with a non-ADD, "
561 f
"which is not supported. "
562 f
"The operation found was "
563 f
"'{ref.debug_string()}'.")
566 f
"Found a dependency index that is a "
567 f
"BinaryOperation where neither child "
568 f
"is a Literal or BinaryOperation. "
569 f
"PSyclone can't validate "
571 f
"The operation found was "
572 f
"'{ref.debug_string()}'.")
574 preceding_nodes, task, symbol)
576 if isinstance(ref, BinaryOperation):
583 f
"Found a dependency between a "
584 f
"BinaryOperation and a previously "
585 f
"set constant value. "
586 f
"PSyclone cannot yet handle this "
587 f
"interaction. The error occurs from "
588 f
"'{ref.debug_string()}'.")
592 if not isinstance(step, Literal):
594 f
"Found a dependency index that is a "
595 f
"Loop variable with a non-Literal step "
596 f
"which we can't resolve in PSyclone. "
597 f
"Containing node is '{ref.debug_string()}'.")
600 if (isinstance(start, Literal)
and isinstance(stop, Literal)):
603 startval = int(start.value)
604 stopval = int(stop.value)
605 stepval = int(step.value)
608 for i
in range(startval, stopval + 1, stepval):
609 new_x = i + binop_val
610 output_list.append(
Literal(f
"{new_x}", INTEGER_TYPE))
618 output_list[
"start"] = BinaryOperation.create(
619 BinaryOperation.Operator.ADD,
621 Literal(f
"{binop_val}", INTEGER_TYPE)
623 output_list[
"stop"] = stop.copy()
624 output_list[
"step"] = step.copy()
628 output_list = [start]
632 if not isinstance(step, Literal):
634 "Found a dependency index that is a "
635 "Loop variable with a non-Literal step "
636 "which we can't resolve in PSyclone.")
638 if (isinstance(start, Literal)
and isinstance(stop, Literal)):
641 startval = int(start.value)
642 stopval = int(stop.value)
643 stepval = int(step.value)
646 for i
in range(startval, stopval + 1, stepval):
647 output_list.append(
Literal(f
"{i}", INTEGER_TYPE))
657 output_list[
"start"] = start.copy()
658 output_list[
"stop"] = stop.copy()
659 output_list[
"step"] = step.copy()
662 def _check_valid_overlap(self, sympy_ref1s, sympy_ref2s):
664 Takes two lists of SymPy expressions, and checks that any overlaps
665 between the expressions is valid for OpenMP depend clauses.
667 :param sympy_ref1s: the list of SymPy expressions corresponding to
668 the first dependency clause.
669 :type sympy_ref1s: List[:py:class:`sympy.core.basic.Basic`]
670 :param sympy_ref2s: the list of SymPy expressions corresponding to
671 the second dependency clause.
672 :type sympy_ref2s: List[:py:class:`sympy.core.basic.Basic`]
674 :returns: whether this is a valid overlap according to the OpenMP \
684 values = [int(member)
for member
in sympy_ref1s]
689 for member
in sympy_ref2s:
693 if r1_min <= val <= r1_max:
694 if val
not in values:
703 def _valid_dependence_ref_binop(self, ref1, ref2, task1, task2):
705 Compares two Reference/BinaryOperation Nodes to check they are a set
706 of dependencies that are valid according to OpenMP. Both these nodes
707 are array indices on the same array symbol, so for OpenMP to correctly
708 compute this dependency, we must guarantee at compile time that we
709 know the addresses/array sections covered by this index are identical.
711 :param ref1: the first Node to compare.
712 :type ref1: Union[:py:class:`psyclone.psyir.nodes.Reference`, \
713 :py:class:`psyclone.psyir.nodes.BinaryOperation`]
714 :param ref2: the second Node to compare.
715 :type ref2: Union[:py:class:`psyclone.psyir.nodes.Reference`, \
716 :py:class:`psyclone.psyir.nodes.BinaryOperation`]
717 :param task1: the task containing ref1 as a child.
718 :type task1: :py:class:`psyclone.psyir.nodes.OMPTaskDirective`
719 :param task2: the task containing ref2 as a child.
720 :type task2: :py:class:`psyclone.psyir.nodes.OMPTaskDirective`
722 :raises GenerationError: If ref1 and ref2 are dependencies on the \
723 same array, and one does not contain a \
724 Reference but the other does.
725 :raises GenerationError: If ref1 and ref2 are dependencies on the \
726 same array but are References to different \
728 :raises GenerationError: If ref1 and ref2 are dependencies on the \
729 same array, but the computed index values \
730 are not dependent according to OpenMP.
732 :returns: whether or not these two nodes can be used as a valid \
733 dependency on the same array in OpenMP.
743 preceding_t1 = task1.preceding(reverse=
True)
744 preceding_t2 = task2.preceding(reverse=
True)
747 ref1_accesses = self.
_compute_accesses_compute_accesses(ref1, preceding_t1, task1)
748 ref2_accesses = self.
_compute_accesses_compute_accesses(ref2, preceding_t2, task2)
749 except UnresolvedDependencyError:
756 sympy_writer = SymPyWriter()
761 if isinstance(ref1_accesses, dict)
or isinstance(ref2_accesses, dict):
764 if type(ref1_accesses)
is not type(ref2_accesses):
768 if ref1_accesses[
"step"] != ref2_accesses[
"step"]:
773 sympy_start1 = sympy_writer(ref1_accesses[
"start"])
774 sympy_start2 = sympy_writer(ref2_accesses[
"start"])
775 sympy_step = sympy_writer(ref2_accesses[
"step"])
776 b_sym = sympy.Symbol(
'b')
777 result = sympy.solvers.solve(sympy_start1 - sympy_start2 +
778 b_sym * sympy_step, b_sym)
779 if not isinstance(result[0], sympy.core.numbers.Integer):
790 sympy_ref1s = sympy_writer(ref1_accesses)
791 sympy_ref2s = sympy_writer(ref2_accesses)
794 def _check_dependency_pairing_valid(self, node1, node2, task1, task2):
796 Given a pair of nodes which are children of a OMPDependClause, this
797 function checks whether the described dependence is correctly
798 described by the OpenMP standard.
799 If the dependence is not going to be handled safely, this function
800 returns False, else it returns true.
802 :param node1: the first input node to check.
803 :type node1: :py:class:`psyclone.psyir.nodes.Reference`
804 :param node2: the second input node to check.
805 :type node2: :py:class:`psyclone.psyir.nodes.Reference`
806 :param task1: the OMPTaskDirective node containing node1 as a \
808 :type task1: :py:class:`psyclone.psyir.nodes.OMPTaskDirective`
809 :param task2: the OMPTaskDirective node containing node2 as a \
811 :type task2: :py:class:`psyclone.psyir.nodes.OMPTaskDirective`
813 :returns: whether the dependence is going to be handled safely \
814 according to the OpenMP standard.
819 if node1.symbol != node2.symbol:
826 if type(node1)
is not type(node2):
830 if isinstance(node1, StructureReference):
840 ref0_sig = node1.get_signature_and_indices()[0]
841 ref1_sig = node2.get_signature_and_indices()[0]
842 if ref0_sig != ref1_sig:
849 if type(node1)
is Reference:
858 if len(node1.walk(ArrayMixin)) > 1
or len(node2.walk(ArrayMixin)) > 1:
860 if isinstance(node1, ArrayReference):
864 array1 = node1.walk(ArrayMixin)[0]
865 array2 = node2.walk(ArrayMixin)[0]
866 for i, index
in enumerate(array1.indices):
867 if (isinstance(index, Literal)
or
868 isinstance(array2.indices[i], Literal)):
870 index, array2.indices[i])
871 elif (isinstance(index, Range)
or
872 isinstance(array2.indices[i], Range)):
879 index, array2.indices[i], task1, task2)
887 def _validate_task_dependencies(self):
889 Validates all task dependencies in this OMPSerialDirective region are
890 valid within the restraints of OpenMP & PSyclone. This is done through
891 a variety of helper functions, and checks each pair of tasks' inout,
892 outin and outout combinations.
894 Any task dependencies that are detected and will not be handled by
895 OpenMP's depend clause will be handled through the addition of
896 OMPTaskwaitDirective nodes.
898 :raises NotImplementedError: If this region contains both an \
899 OMPTaskDirective and an OMPTaskloopDirective.
903 tasks = self.
walkwalk(OMPTaskDirective)
906 if len(tasks) > 0
and any(self.
walkwalk(OMPTaskloopDirective)):
907 raise NotImplementedError(
"OMPTaskDirectives and "
908 "OMPTaskloopDirectives are not "
909 "currently supported inside the same "
910 "parent serial region.")
912 pairs = itertools.combinations(tasks, 2)
915 unhandled_dependent_nodes = []
919 lowest_position_nodes = []
920 highest_position_nodes = []
928 task1_in = [x
for x
in task1.input_depend_clause.children
929 if isinstance(x, Reference)]
930 task1_out = [x
for x
in task1.output_depend_clause.children
931 if isinstance(x, Reference)]
932 task2_in = [x
for x
in task2.input_depend_clause.children
933 if isinstance(x, Reference)]
934 task2_out = [x
for x
in task2.output_depend_clause.children
935 if isinstance(x, Reference)]
937 inout = list(itertools.product(task1_in, task2_out))
938 outin = list(itertools.product(task1_out, task2_in))
939 outout = list(itertools.product(task1_out, task2_out))
945 for mem
in inout + outin + outout:
961 schedule1 = task1.ancestor(Schedule, shared_with=task2)
964 while task1_proxy.parent
is not schedule1:
965 task1_proxy = task1_proxy.parent
967 while task2_proxy.parent
is not schedule1:
968 task2_proxy = task2_proxy.parent
972 if task1_proxy
is not task2_proxy:
979 unhandled_dependent_nodes.append(
980 (task1_proxy, task2_proxy))
981 lowest_position_nodes.append(min(task1_proxy.abs_position,
982 task2_proxy.abs_position))
983 highest_position_nodes.append(
984 max(task1_proxy.abs_position,
985 task2_proxy.abs_position))
988 if len(unhandled_dependent_nodes) == 0:
1003 sorted_highest_positions, sorted_lowest_positions, \
1004 sorted_dependency_pairs = (list(t)
for t
in
1006 highest_position_nodes,
1007 lowest_position_nodes,
1008 unhandled_dependent_nodes)
1016 taskwait_location_nodes = []
1019 taskwait_location_abs_pos = []
1020 for taskwait
in self.
walkwalk(OMPTaskwaitDirective):
1021 taskwait_location_nodes.append(taskwait)
1022 taskwait_location_abs_pos.append(taskwait.abs_position)
1026 lo_abs_pos = sorted_lowest_positions[0]
1027 hi_abs_pos = sorted_highest_positions[0]
1028 for ind, taskwait_loc
in enumerate(taskwait_location_nodes):
1029 if (taskwait_location_abs_pos[ind] <= hi_abs_pos
and
1030 taskwait_location_abs_pos[ind] >= lo_abs_pos):
1032 if (sorted_dependency_pairs[0][1].
ancestor(Schedule)
is
1033 taskwait_loc.ancestor(Schedule)):
1036 taskwait_location_nodes.append(sorted_dependency_pairs[0][1])
1037 taskwait_location_abs_pos.append(sorted_highest_positions[0])
1039 for index, pairs
in enumerate(sorted_dependency_pairs[1:]):
1041 lo_abs_pos = sorted_lowest_positions[index+1]
1042 hi_abs_pos = sorted_highest_positions[index+1]
1043 for ind, taskwait_loc
in enumerate(taskwait_location_nodes):
1044 if (taskwait_location_abs_pos[ind] <= hi_abs_pos
and
1045 taskwait_location_abs_pos[ind] >= lo_abs_pos):
1051 taskwait_loc.ancestor(Schedule)):
1056 taskwait_location_nodes.append(pairs[1])
1057 taskwait_location_abs_pos.append(hi_abs_pos)
1060 taskwait_location_nodes.reverse()
1061 for taskwait_loc
in taskwait_location_nodes:
1062 if isinstance(taskwait_loc, OMPTaskwaitDirective):
1064 node_parent = taskwait_loc.parent
1065 loc = taskwait_loc.position
1070 Checks that any task dependencies inside this node are valid.
1080 Perform validation checks that can only be done at code-generation
1083 :raises GenerationError: if this OMPSerial is not enclosed \
1084 within some OpenMP parallel region.
1085 :raises GenerationError: if this OMPSerial is enclosed within \
1086 any OMPSerialDirective subclass region.
1099 if not self.
ancestorancestor(OMPParallelDirective,
1100 excluding=OMPParallelDoDirective):
1102 f
"{self._text_name} must be inside an OMP parallel region but "
1103 f
"could not find an ancestor OMPParallelDirective node")
1105 if self.
ancestorancestor(OMPSerialDirective):
1107 f
"{self._text_name} must not be inside another OpenMP "
1115 Class representing an OpenMP SINGLE directive in the PSyIR.
1117 :param bool nowait: argument describing whether this single should have \
1118 a nowait clause applied. Default value is False.
1119 :param kwargs: additional keyword arguments provided to the PSyIR node.
1120 :type kwargs: unwrapped dict.
1123 _children_valid_format =
"Schedule, [OMPNowaitClause]"
1125 _text_name =
"OMPSingleDirective"
1127 def __init__(self, nowait=False, **kwargs):
1132 super().__init__(**kwargs)
1137 def _validate_child(position, child):
1139 Decides whether a given child and position are valid for this node.
1141 1. Child 0 must always be a Schedule.
1142 2. Child 1 can only be a OMPNowaitClause.
1144 :param int position: the position to be validated.
1145 :param child: a child to be validated.
1146 :type child: :py:class:`psyclone.psyir.nodes.Node`
1148 :return: whether the given child and position are valid for this node.
1153 return isinstance(child, Schedule)
1155 return isinstance(child, OMPNowaitClause)
1161 :returns: whether the nowait clause is specified for this directive.
1168 '''Generate the fortran OMP Single Directive and any associated
1171 :param parent: the parent Node in the Schedule to which to add our \
1173 :type parent: sub-class of :py:class:`psyclone.f2pygen.BaseGen`
1181 nowait_string =
"nowait"
1183 parent.add(
DirectiveGen(parent,
"omp",
"begin",
"single",
1187 for child
in self.
dir_bodydir_body:
1188 child.gen_code(parent)
1191 parent.add(
DirectiveGen(parent,
"omp",
"end",
"single",
""))
1194 '''Returns the beginning statement of this directive, i.e.
1195 "omp single". The visitor is responsible for adding the
1196 correct directive beginning (e.g. "!$").
1198 :returns: the opening statement of this directive.
1205 '''Returns the end (or closing) statement of this directive, i.e.
1206 "omp end single". The visitor is responsible for adding the
1207 correct directive beginning (e.g. "!$").
1209 :returns: the end statement for this directive.
1213 return "omp end single"
1218 Class representing an OpenMP MASTER directive in the PSyclone AST.
1223 _text_name =
"OMPMasterDirective"
1226 '''Generate the Fortran OMP Master Directive and any associated
1229 :param parent: the parent Node in the Schedule to which to add our \
1231 :type parent: sub-class of :py:class:`psyclone.f2pygen.BaseGen`
1237 parent.add(
DirectiveGen(parent,
"omp",
"begin",
"master",
""))
1241 child.gen_code(parent)
1244 parent.add(
DirectiveGen(parent,
"omp",
"end",
"master",
""))
1247 '''Returns the beginning statement of this directive, i.e.
1248 "omp master". The visitor is responsible for adding the
1249 correct directive beginning (e.g. "!$").
1251 :returns: the opening statement of this directive.
1258 '''Returns the end (or closing) statement of this directive, i.e.
1259 "omp end master". The visitor is responsible for adding the
1260 correct directive beginning (e.g. "!$").
1262 :returns: the end statement for this directive.
1266 return "omp end master"
1270 ''' Class representing an OpenMP Parallel directive.
1273 _children_valid_format = (
"Schedule, OMPDefaultClause, OMPPrivateClause, "
1274 "OMPFirstprivate, [OMPReductionClause]*")
1279 Create an OMPParallelDirective.
1281 :param children: The child nodes of the new directive.
1282 :type children: List of :py:class:`psyclone.psyir.nodes.Node`
1284 :returns: A new OMPParallelDirective.
1285 :rtype: :py:class:`psyclone.psyir.nodes.OMPParallelDirective`
1295 DefaultClauseTypes.SHARED))
1302 def _validate_child(position, child):
1304 :param int position: the position to be validated.
1305 :param child: a child to be validated.
1306 :type child: :py:class:`psyclone.psyir.nodes.Node`
1308 :return: whether the given child and position are valid for this node.
1312 if position == 0
and isinstance(child, Schedule):
1314 if position == 1
and isinstance(child, OMPDefaultClause):
1316 if position == 2
and isinstance(child, OMPPrivateClause):
1318 if position == 3
and isinstance(child, OMPFirstprivateClause):
1320 if position >= 4
and isinstance(child, OMPReductionClause):
1327 :returns: The OMPDefaultClause associated with this Directive.
1328 :rtype: :py:class:`psyclone.psyir.nodes.OMPDefaultClause`
1335 :returns: The current OMPPrivateClause associated with this Directive.
1336 :rtype: :py:class:`psyclone.psyir.nodes.OMPPrivateClause`
1341 '''Generate the fortran OMP Parallel Directive and any associated
1344 :param parent: the node in the generated AST to which to add content.
1345 :type parent: :py:class:`psyclone.f2pygen.BaseGen`
1347 :raises GenerationError: if the OpenMP directive needs some
1348 synchronisation mechanism to create valid code. These are not
1366 private_clause = OMPPrivateClause.create(
1367 sorted(private, key=
lambda x: x.name))
1368 fprivate_clause = OMPFirstprivateClause.create(
1369 sorted(fprivate, key=
lambda x: x.name))
1372 f
"OMPParallelDirective.gen_code() does not support symbols "
1373 f
"that need synchronisation, but found: "
1374 f
"{[x.name for x in need_sync]}")
1376 reprod_red_call_list = self.
reductionsreductions(reprod=
True)
1377 if reprod_red_call_list:
1379 thread_idx = self.
scopescope.symbol_table.\
1380 lookup_with_tag(
"omp_thread_index")
1381 private_clause.addchild(
Reference(thread_idx))
1382 thread_idx = thread_idx.name
1384 parent.add(
DeclGen(parent, datatype=
"integer",
1385 entity_decls=[thread_idx]))
1394 name = call.reduction_arg.name
1397 f
"Reduction variables can only be used once in an invoke. "
1398 f
"'{name}' is used multiple times, please use a different "
1399 f
"reduction variable")
1402 zero_reduction_variables(calls, parent)
1408 private_list = [child.symbol.name
for child
in private_clause.children]
1410 clauses_str +=
", private(" +
",".join(private_list) +
")"
1411 fp_list = [child.symbol.name
for child
in fprivate_clause.children]
1413 clauses_str +=
", firstprivate(" +
",".join(fp_list) +
")"
1414 parent.add(
DirectiveGen(parent,
"omp",
"begin",
"parallel",
1417 if reprod_red_call_list:
1419 parent.add(
UseGen(parent, name=
"omp_lib", only=
True,
1420 funcnames=[
"omp_get_thread_num"]))
1421 parent.add(
AssignGen(parent, lhs=thread_idx,
1422 rhs=
"omp_get_thread_num()+1"))
1424 first_type = type(self.
dir_bodydir_body[0])
1425 for child
in self.
dir_bodydir_body.children:
1426 if first_type != type(child):
1427 raise NotImplementedError(
"Cannot correctly generate code"
1428 " for an OpenMP parallel region"
1429 " containing children of "
1431 child.gen_code(parent)
1433 parent.add(
DirectiveGen(parent,
"omp",
"end",
"parallel",
""))
1435 if reprod_red_call_list:
1437 parent.add(
CommentGen(parent,
" sum the partial results "
1440 for call
in reprod_red_call_list:
1441 call.reduction_sum_loop(parent)
1447 In-place construction of clauses as PSyIR constructs.
1448 At the higher level these clauses rely on dynamic variable dependence
1449 logic to decide what is private and what is shared, so we use this
1450 lowering step to find out which References are private, and place them
1451 explicitly in the lower-level tree to be processed by the backend
1454 :returns: the lowered version of this node.
1455 :rtype: :py:class:`psyclone.psyir.node.Node`
1457 :raises GenerationError: if the OpenMP directive needs some
1458 synchronisation mechanism to create valid code. These are not
1466 child.lower_to_language_level()
1471 private_clause = OMPPrivateClause.create(
1472 sorted(private, key=
lambda x: x.name))
1473 fprivate_clause = OMPFirstprivateClause.create(
1474 sorted(fprivate, key=
lambda x: x.name))
1476 sync_clauses = self.
walkwalk(OMPDependClause)
1478 for sym
in need_sync:
1480 for clause
in sync_clauses:
1482 if clause.operand ==
"in":
1485 if sym.name
in [child.symbol.name
for child
in
1492 f
"Lowering '{type(self).__name__}' does not support "
1493 f
"symbols that need synchronisation unless they are "
1494 f
"in a depend clause, but found: "
1495 f
"'{sym.name}' which is not in a depend clause.")
1497 self.
addchildaddchild(private_clause)
1498 self.
addchildaddchild(fprivate_clause)
1502 '''Returns the beginning statement of this directive, i.e.
1503 "omp parallel". The visitor is responsible for adding the
1504 correct directive beginning (e.g. "!$").
1506 :returns: the opening statement of this directive.
1510 result =
"omp parallel"
1518 '''Returns the end (or closing) statement of this directive, i.e.
1519 "omp end parallel". The visitor is responsible for adding the
1520 correct directive beginning (e.g. "!$").
1522 :returns: the end statement for this directive.
1526 return "omp end parallel"
1530 The PSyIR does not specify if each symbol inside an OpenMP region is
1531 private, firstprivate, shared or shared but needs synchronisation,
1532 the attributes are inferred looking at the usage of each symbol inside
1533 the parallel region.
1535 This method analyses the directive body and automatically classifies
1536 each symbol using the following rules:
1537 - All arrays are shared.
1538 - Scalars that are accessed only once are shared.
1539 - Scalars that are read-only or written outside a loop are shared.
1540 - Scalars written in multiple iterations of a loop are private, unless:
1542 * there is a write-after-read dependency in a loop iteration,
1543 in this case they are shared but need synchronisation;
1544 * they are read before in the same parallel region (but not inside
1545 the same loop iteration), in this case they are firstprivate.
1546 * they are only conditionally written in some iterations;
1547 in this case they are firstprivate.
1549 This method returns the sets of private, firstprivate, and shared but
1550 needing synchronisation symbols, all symbols not in these sets are
1551 assumed shared. How to synchronise the symbols in the third set is
1552 up to the caller of this method.
1554 :returns: three set of symbols that classify each of the symbols in
1555 the directive body as PRIVATE, FIRSTPRIVATE or SHARED NEEDING
1557 :rtype: Tuple[Set(:py:class:`psyclone.psyir.symbols.Symbol`),
1558 Set(:py:class:`psyclone.psyir.symbols.Symbol`),
1559 Set(:py:class:`psyclone.psyir.symbols.Symbol`)]
1561 :raises GenerationError: if the DefaultClauseType associated with
1562 this OMPParallelDirective is not shared.
1566 OMPDefaultClause.DefaultClauseTypes.SHARED):
1568 " the private clause when its default "
1569 "data sharing attribute in its default "
1570 "clause is not 'shared'.")
1592 for signature
in var_accesses.all_signatures:
1593 accesses = var_accesses[signature].all_accesses
1595 if accesses[0].is_array():
1600 if len(accesses) == 1:
1615 has_been_read =
False
1616 last_read_position = 0
1617 for access
in accesses:
1618 if access.access_type == AccessType.READ:
1619 has_been_read =
True
1620 last_read_position = access.node.abs_position
1622 if access.access_type == AccessType.WRITE:
1631 loop_ancestor = access.node.ancestor(
1635 if not loop_ancestor:
1643 name = signature.var_name
1648 symbol = access.node.scope.symbol_table.lookup(name)
1652 loop_pos = loop_ancestor.loop_body.abs_position
1653 if last_read_position < loop_pos:
1655 fprivate.add(symbol)
1658 need_sync.add(symbol)
1664 conditional_write = access.node.ancestor(
1666 limit=loop_ancestor,
1668 if conditional_write:
1669 fprivate.add(symbol)
1677 return private, fprivate, need_sync
1681 Perform validation checks that can only be done at code-generation
1684 :raises GenerationError: if this OMPDoDirective is not enclosed \
1685 within some OpenMP parallel region.
1687 if self.
ancestorancestor(OMPParallelDirective)
is not None:
1691 def _encloses_omp_directive(self):
1692 ''' Check that this Parallel region contains other OpenMP
1693 directives. While it doesn't have to (in order to be valid
1694 OpenMP), it is likely that an absence of directives
1695 is an error on the part of the user. '''
1698 node_list = self.
walkwalk(OMPRegionDirective)
1710 Class representing an OpenMP TASKLOOP directive in the PSyIR.
1712 :param grainsize: The grainsize value used to specify the grainsize \
1713 clause on this OpenMP directive. If this is None \
1714 the grainsize clause is not applied. Default \
1716 :type grainsize: int or None.
1717 :param num_tasks: The num_tasks value used to specify the num_tasks \
1718 clause on this OpenMP directive. If this is None \
1719 the num_tasks clause is not applied. Default value \
1721 :type num_tasks: int or None.
1722 :param nogroup: Whether the nogroup clause should be used for this node. \
1723 Default value is False
1725 :param kwargs: additional keyword arguments provided to the PSyIR node.
1726 :type kwargs: unwrapped dict.
1728 :raises GenerationError: if this OMPTaskloopDirective has both \
1729 a grainsize and num_tasks value \
1736 _children_valid_format = (
"Schedule, [OMPGrainsizeClause | "
1737 "OMPNumTasksClause], [OMPNogroupClause]")
1739 def __init__(self, grainsize=None, num_tasks=None, nogroup=False,
1747 "OMPTaskloopDirective must not have both grainsize and "
1748 "numtasks clauses specified.")
1749 super().__init__(**kwargs)
1751 child = [
Literal(f
"{grainsize}", INTEGER_TYPE)]
1754 child = [
Literal(f
"{num_tasks}", INTEGER_TYPE)]
1760 def _validate_child(position, child):
1762 Decides whether a given child and position are valid for this node.
1764 1. Child 0 must always be a Schedule.
1765 2. Child 1 may be either a OMPGrainsizeClause or OMPNumTasksClause, \
1766 or if neither of those clauses are present, it may be a \
1768 3. Child 2 must always be a OMPNogroupClause, and can only exist if \
1769 child 1 is a OMPGrainsizeClause or OMPNumTasksClause.
1771 :param int position: the position to be validated.
1772 :param child: a child to be validated.
1773 :type child: :py:class:`psyclone.psyir.nodes.Node`
1775 :return: whether the given child and position are valid for this node.
1780 return isinstance(child, Schedule)
1782 return isinstance(child, (OMPGrainsizeClause, OMPNumTasksClause,
1785 return isinstance(child, OMPNogroupClause)
1791 :returns: the nogroup clause status of this node.
1798 Perform validation checks that can only be done at code-generation
1801 :raises GenerationError: if this OMPTaskloopDirective is not \
1802 enclosed within an OpenMP serial region.
1803 :raises GenerationError: if this OMPTaskloopDirective has two
1804 Nogroup clauses as children.
1810 if not self.
ancestorancestor(OMPSerialDirective):
1812 "OMPTaskloopDirective must be inside an OMP Serial region "
1813 "but could not find an ancestor node")
1820 "OMPTaskloopDirective has two Nogroup clauses as children "
1821 "which is not allowed.")
1827 Generate the f2pygen AST entries in the Schedule for this OpenMP
1830 :param parent: the parent Node in the Schedule to which to add our \
1832 :type parent: sub-class of :py:class:`psyclone.f2pygen.BaseGen`
1833 :raises GenerationError: if this "!$omp taskloop" is not enclosed \
1834 within an OMP Parallel region and an OMP \
1844 clause_list.append(f
"grainsize({self._grainsize})")
1846 clause_list.append(f
"num_tasks({self._num_tasks})")
1848 clause_list.append(
"nogroup")
1850 extra_clauses =
", ".join(clause_list)
1852 parent.add(
DirectiveGen(parent,
"omp",
"begin",
"taskloop",
1858 position = parent.previous_loop()
1859 parent.add(
DirectiveGen(parent,
"omp",
"end",
"taskloop",
""),
1860 position=[
"after", position])
1863 '''Returns the beginning statement of this directive, i.e.
1864 "omp taskloop ...". The visitor is responsible for adding the
1865 correct directive beginning (e.g. "!$").
1867 :returns: the beginning statement for this directive.
1871 return "omp taskloop"
1874 '''Returns the end (or closing) statement of this directive, i.e.
1875 "omp end taskloop". The visitor is responsible for adding the
1876 correct directive beginning (e.g. "!$").
1878 :returns: the end statement for this directive.
1882 return "omp end taskloop"
1887 Class representing an OpenMP DO directive in the PSyIR.
1889 :param str omp_schedule: the OpenMP schedule to use (defaults to
1890 "none" which means it is implementation dependent).
1891 :param Optional[int] collapse: optional number of nested loops to \
1892 collapse into a single iteration space to parallelise. Defaults to \
1894 :param Optional[bool] reprod: whether or not to generate code for \
1895 run-reproducible OpenMP reductions (if not specified the value is \
1896 provided by the PSyclone Config file).
1897 :param kwargs: additional keyword arguments provided to the PSyIR node.
1898 :type kwargs: unwrapped dict.
1901 _directive_string =
"do"
1903 def __init__(self, omp_schedule="none", collapse=None, reprod=None,
1906 super().__init__(**kwargs)
1908 self.
_reprod_reprod = Config.get().reproducible_reductions
1918 Checks whether two nodes are equal. Two OMPDoDirective nodes are equal
1919 if they have the same schedule, the same reproducible reduction option
1920 (and the inherited equality is True).
1922 :param object other: the object to check equality to.
1924 :returns: whether other is equal to self.
1927 is_eq = super().
__eq__(other)
1937 :returns: the value of the collapse clause.
1938 :rtype: int or NoneType
1943 def collapse(self, value):
1945 TODO #1648: Note that gen_code ignores the collapse clause but the
1946 generated code is still valid. Since gen_code is going to be removed
1947 and it is only used for LFRic (which does not support GPU offloading
1948 that gets improved with the collapse clause) it will not be supported.
1950 :param value: optional number of nested loop to collapse into a \
1951 single iteration space to parallelise. Defaults to None.
1952 :type value: int or NoneType.
1954 :raises TypeError: if the collapse value given is not an integer \
1956 :raises ValueError: if the collapse integer given is not positive.
1959 if value
is not None and not isinstance(value, int):
1961 f
"The {type(self).__name__} collapse clause must be a positive"
1962 f
" integer or None, but value '{value}' has been given.")
1964 if value
is not None and value <= 0:
1966 f
"The {type(self).__name__} collapse clause must be a positive"
1967 f
" integer or None, but value '{value}' has been given.")
1973 Returns the name of this node with (optional) control codes
1974 to generate coloured output in a terminal that supports it.
1976 :param bool colour: whether or not to include colour control codes.
1978 :returns: description of this node, possibly coloured.
1983 parts.append(f
"omp_schedule={self.omp_schedule}")
1985 parts.append(f
"reprod={self._reprod}")
1987 parts.append(f
"collapse={self._collapse}")
1988 return f
"{self.coloured_name(colour)}[{','.join(parts)}]"
1990 def _reduction_string(self):
1992 :returns: the OMP reduction information.
1995 for reduction_type
in AccessType.get_valid_reduction_modes():
1998 for reduction
in reductions:
1999 parts.append(f
"reduction("
2000 f
"{OMP_OPERATOR_MAPPING[reduction_type]}:"
2002 return ", ".join(parts)
2005 def omp_schedule(self):
2007 :returns: the omp_schedule for this object.
2012 @omp_schedule.setter
2013 def omp_schedule(self, value):
2015 :param str value: the omp_schedule for this object.
2017 :raises TypeError: if the provided omp_schedule is not a valid \
2020 if not isinstance(value, str):
2022 f
"{type(self).__name__} omp_schedule should be a str "
2023 f
"but found '{type(value).__name__}'.")
2024 if (value.split(
',')[0].lower()
not in
2025 OMPScheduleClause.VALID_OMP_SCHEDULES):
2027 f
"{type(self).__name__} omp_schedule should be one of "
2028 f
"{OMPScheduleClause.VALID_OMP_SCHEDULES} but found "
2035 :returns: whether reprod has been set for this object or not.
2040 def reprod(self, value):
2042 :param bool value: enable or disable reproducible loop parallelism.
2048 Perform validation checks that can only be done at code-generation
2051 :raises GenerationError: if this OMPDoDirective is not enclosed \
2052 within some OpenMP parallel region.
2059 if not self.
ancestorancestor(OMPParallelDirective,
2060 excluding=OMPParallelDoDirective):
2062 "OMPDoDirective must be inside an OMP parallel region but "
2063 "could not find an ancestor OMPParallelDirective node")
2070 def _validate_collapse_value(self):
2072 Checks that if there is a collapse clause, there must be as many
2073 immediately nested loops as the collapse value.
2075 :raises GenerationError: if this OMPLoopDirective has a collapse \
2076 clause but it doesn't have the expected number of nested Loops.
2079 cursor = self.
dir_bodydir_body.children[0]
2080 for depth
in range(self.
_collapse_collapse):
2081 if (len(cursor.parent.children) != 1
or
2082 not isinstance(cursor, Loop)):
2084 f
"{type(self).__name__} must have as many immediately "
2085 f
"nested loops as the collapse clause specifies but "
2086 f
"'{self}' has a collapse={self._collapse} and the "
2087 f
"nested body at depth {depth} cannot be "
2089 cursor = cursor.loop_body.children[0]
2091 def _validate_single_loop(self):
2093 Checks that this directive is only applied to a single Loop node.
2095 :raises GenerationError: if this directive has more than one child.
2096 :raises GenerationError: if the child of this directive is not a Loop.
2099 if len(self.
dir_bodydir_body.children) != 1:
2101 f
"An {type(self).__name__} can only be applied to a single "
2102 f
"loop but this Node has {len(self.dir_body.children)} "
2103 f
"children: {self.dir_body.children}")
2105 if not isinstance(self.
dir_bodydir_body[0], Loop):
2107 f
"An {type(self).__name__} can only be applied to a loop but "
2108 f
"this Node has a child of type "
2109 f
"'{type(self.dir_body[0]).__name__}'")
2113 Generate the f2pygen AST entries in the Schedule for this OpenMP do
2116 TODO #1648: Note that gen_code ignores the collapse clause but the
2117 generated code is still valid. Since gen_code is going to be removed
2118 and it is only used for LFRic (which does not support GPU offloading
2119 that gets improved with the collapse clause) it will not be supported.
2121 :param parent: the parent Node in the Schedule to which to add our \
2123 :type parent: sub-class of :py:class:`psyclone.f2pygen.BaseGen`
2124 :raises GenerationError: if this "!$omp do" is not enclosed within \
2125 an OMP Parallel region.
2133 parts.append(f
"schedule({self.omp_schedule})")
2138 parts.append(red_str)
2143 options =
", ".join(parts)
2144 parent.add(
DirectiveGen(parent,
"omp",
"begin",
"do", options))
2147 child.gen_code(parent)
2150 position = parent.previous_loop()
2151 parent.add(
DirectiveGen(parent,
"omp",
"end",
"do",
""),
2152 position=[
"after", position])
2155 '''Returns the beginning statement of this directive, i.e.
2156 "omp do ...". The visitor is responsible for adding the
2157 correct directive beginning (e.g. "!$").
2159 :returns: the beginning statement for this directive.
2163 string = f
"omp {self._directive_string}"
2165 string += f
" schedule({self.omp_schedule})"
2167 string += f
" collapse({self._collapse})"
2171 '''Returns the end (or closing) statement of this directive, i.e.
2172 "omp end do". The visitor is responsible for adding the
2173 correct directive beginning (e.g. "!$").
2175 :returns: the end statement for this directive.
2179 return f
"omp end {self._directive_string}"
2183 ''' Class for the !$OMP PARALLEL DO directive. This inherits from
2184 both OMPParallelDirective (because it creates a new OpenMP
2185 thread-parallel region) and OMPDoDirective (because it
2186 causes a loop to be parallelised).
2188 :param kwargs: additional keyword arguments provided to the PSyIR node.
2189 :type kwargs: unwrapped dict.
2192 _children_valid_format = (
"Schedule, OMPDefaultClause, OMPPrivateClause, "
2193 "OMPFirstprivateClause, OMPScheduleClause, "
2194 "[OMPReductionClause]*")
2195 _directive_string =
"parallel do"
2197 def __init__(self, **kwargs):
2198 OMPDoDirective.__init__(self, **kwargs)
2200 clause_type=OMPDefaultClause.DefaultClauseTypes.SHARED))
2203 def _validate_child(position, child):
2205 :param int position: the position to be validated.
2206 :param child: a child to be validated.
2207 :type child: :py:class:`psyclone.psyir.nodes.Node`
2209 :return: whether the given child and position are valid for this node.
2213 if position == 0
and isinstance(child, Schedule):
2215 if position == 1
and isinstance(child, OMPDefaultClause):
2217 if position == 2
and isinstance(child, OMPPrivateClause):
2219 if position == 3
and isinstance(child, OMPFirstprivateClause):
2221 if position == 4
and isinstance(child, OMPScheduleClause):
2223 if position >= 5
and isinstance(child, OMPReductionClause):
2229 Generate the f2pygen AST entries in the Schedule for this OpenMP
2232 TODO #1648: Note that gen_code ignores the collapse clause but the
2233 generated code is still valid. Since gen_code is going to be removed
2234 and it is only used for LFRic (which does not support GPU offloading
2235 that gets improved with the collapse clause) it will not be supported.
2237 :param parent: the parent Node in the Schedule to which to add our \
2239 :type parent: sub-class of :py:class:`psyclone.f2pygen.BaseGen`
2249 zero_reduction_variables(calls, parent)
2256 private_clause = OMPPrivateClause.create(
2257 sorted(private, key=
lambda x: x.name))
2258 fprivate_clause = OMPFirstprivateClause.create(
2259 sorted(fprivate, key=
lambda x: x.name))
2262 f
"OMPParallelDoDirective.gen_code() does not support symbols "
2263 f
"that need synchronisation, but found: "
2264 f
"{[x.name for x in need_sync]}")
2268 private_list = [child.symbol.name
for child
in private_clause.children]
2270 private_str =
"private(" +
",".join(private_list) +
")"
2271 fp_list = [child.symbol.name
for child
in fprivate_clause.children]
2273 fprivate_str =
"firstprivate(" +
",".join(fp_list) +
")"
2277 schedule_str = f
"schedule({self._omp_schedule})"
2284 parent,
"omp",
"begin",
"parallel do",
", ".join(
2285 text
for text
in [default_str, private_str, fprivate_str,
2289 for child
in self.
dir_bodydir_body:
2290 child.gen_code(parent)
2293 position = parent.previous_loop()
2295 position=[
"after", position])
2301 In-place construction of clauses as PSyIR constructs.
2302 The clauses here may need to be updated if code has changed, or be
2303 added if not yet present.
2305 :returns: the lowered version of this node.
2306 :rtype: :py:class:`psyclone.psyir.node.Node`
2311 OMPParallelDirective.lower_to_language_level(self)
2316 '''Returns the beginning statement of this directive, i.e.
2317 "omp parallel do ...". The visitor is responsible for adding the
2318 correct directive beginning (e.g. "!$").
2320 :returns: the beginning statement for this directive.
2324 string = f
"omp {self._directive_string}"
2326 string += f
" collapse({self._collapse})"
2331 '''Returns the end (or closing) statement of this directive, i.e.
2332 "omp end parallel do". The visitor is responsible for adding the
2333 correct directive beginning (e.g. "!$").
2335 :returns: the end statement for this directive.
2339 return f
"omp end {self._directive_string}"
2343 Perform validation checks that can only be done at code-generation
2347 OMPParallelDirective.validate_global_constraints(self)
2354 ''' Class representing the OMP teams distribute parallel do directive. '''
2355 _directive_string =
"teams distribute parallel do"
2359 ''' Class for the !$OMP TARGET directive that offloads the code contained
2360 in its region into an accelerator device. '''
2363 '''Returns the beginning statement of this directive, i.e.
2364 "omp target". The visitor is responsible for adding the
2365 correct directive beginning (e.g. "!$").
2367 :returns: the opening statement of this directive.
2374 '''Returns the end (or closing) statement of this directive, i.e.
2375 "omp end target". The visitor is responsible for adding the
2376 correct directive beginning (e.g. "!$").
2378 :returns: the end statement for this directive.
2382 return "omp end target"
2386 ''' Class for the !$OMP LOOP directive that specifies that the iterations
2387 of the associated loops may execute concurrently.
2389 :param Optional[int] collapse: optional number of nested loops to \
2390 collapse into a single iteration space to parallelise. Defaults \
2392 :param kwargs: additional keyword arguments provided to the PSyIR node.
2393 :type kwargs: unwrapped dict.
2396 def __init__(self, collapse=None, **kwargs):
2397 super().__init__(**kwargs)
2403 Checks whether two nodes are equal. Two OMPLoopDirective nodes are
2404 equal if they have the same collapse status and the inherited
2407 :param object other: the object to check equality to.
2409 :returns: whether other is equal to self.
2412 is_eq = super().
__eq__(other)
2420 :returns: the value of the collapse clause.
2421 :rtype: int or NoneType
2426 def collapse(self, value):
2428 TODO #1648: Note that gen_code ignores the collapse clause but the
2429 generated code is still valid. Since gen_code is going to be removed
2430 and it is only used for LFRic (which does not support GPU offloading
2431 that gets improved with the collapse clause) it will not be supported.
2433 :param value: optional number of nested loop to collapse into a \
2434 single iteration space to parallelise. Defaults to None.
2435 :type value: int or NoneType.
2437 :raises TypeError: if the collapse value given is not an integer \
2439 :raises ValueError: if the collapse integer given is not positive.
2442 if value
is not None and not isinstance(value, int):
2444 f
"The OMPLoopDirective collapse clause must be a positive "
2445 f
"integer or None, but value '{value}' has been given.")
2447 if value
is not None and value <= 0:
2449 f
"The OMPLoopDirective collapse clause must be a positive "
2450 f
"integer or None, but value '{value}' has been given.")
2455 ''' Returns the name of this node with (optional) control codes
2456 to generate coloured output in a terminal that supports it.
2458 :param bool colour: whether or not to include colour control codes.
2460 :returns: description of this node, possibly coloured.
2465 text += f
"[collapse={self._collapse}]"
2471 ''' Returns the beginning statement of this directive, i.e. "omp loop".
2472 The visitor is responsible for adding the correct directive beginning
2475 :returns: the opening statement of this directive.
2481 string += f
" collapse({self._collapse})"
2485 '''Returns the end (or closing) statement of this directive, i.e.
2486 "omp end loop". The visitor is responsible for adding the
2487 correct directive beginning (e.g. "!$").
2489 :returns: the end statement for this directive.
2493 return "omp end loop"
2496 ''' Perform validation of those global constraints that can only be
2497 done at code-generation time.
2499 :raises GenerationError: if this OMPLoopDirective has more than one \
2500 child in its associated schedule.
2501 :raises GenerationError: if the schedule associated with this \
2502 OMPLoopDirective does not contain a Loop.
2503 :raises GenerationError: this directive must be inside a omp target \
2505 :raises GenerationError: if this OMPLoopDirective has a collapse \
2506 clause but it doesn't have the expected number of nested Loops.
2509 if len(self.
dir_bodydir_body.children) != 1:
2511 f
"OMPLoopDirective must have exactly one child in its "
2512 f
"associated schedule but found {self.dir_body.children}.")
2514 if not isinstance(self.
dir_bodydir_body.children[0], Loop):
2516 f
"OMPLoopDirective must have a Loop as child of its associated"
2517 f
" schedule but found '{self.dir_body.children[0]}'.")
2519 if not self.
ancestorancestor((OMPTargetDirective, OMPParallelDirective)):
2523 f
"OMPLoopDirective must be inside a OMPTargetDirective or a "
2524 f
"OMPParallelDirective, but '{self}' is not.")
2529 cursor = self.
dir_bodydir_body.children[0]
2530 for depth
in range(self.
_collapse_collapse):
2531 if not isinstance(cursor, Loop):
2533 f
"OMPLoopDirective must have as many immediately "
2534 f
"nested loops as the collapse clause specifies but "
2535 f
"'{self}' has a collapse={self._collapse} and the "
2536 f
"nested statement at depth {depth} is a "
2537 f
"{type(cursor).__name__} rather than a Loop.")
2538 cursor = cursor.loop_body.children[0]
2545 OpenMP directive to represent that the memory accesses in the associated
2546 assignment must be performed atomically.
2547 Note that the standard supports blocks with 2 assignments but this is
2548 currently unsupported in the PSyIR.
2553 :returns: the opening string statement of this directive.
2561 :returns: the ending string statement of this directive.
2565 return "omp end atomic"
2569 ''' Check if a given statement is a valid OpenMP atomic expression. See
2570 https://www.openmp.org/spec-html/5.0/openmpsu95.html
2572 :param stmt: a node to be validated.
2573 :type stmt: :py:class:`psyclone.psyir.nodes.Node`
2575 :returns: whether a given statement is compliant with the OpenMP
2580 if not isinstance(stmt, Assignment):
2585 if not isinstance(stmt.lhs.datatype, ScalarType):
2589 if isinstance(stmt.rhs, BinaryOperation):
2590 if stmt.rhs.operator
not in (BinaryOperation.Operator.ADD,
2591 BinaryOperation.Operator.SUB,
2592 BinaryOperation.Operator.MUL,
2593 BinaryOperation.Operator.DIV,
2594 BinaryOperation.Operator.AND,
2595 BinaryOperation.Operator.OR,
2596 BinaryOperation.Operator.EQV,
2597 BinaryOperation.Operator.NEQV):
2600 if isinstance(stmt.rhs, IntrinsicCall):
2601 if stmt.rhs.intrinsic
not in (IntrinsicCall.Intrinsic.MAX,
2602 IntrinsicCall.Intrinsic.MIN,
2603 IntrinsicCall.Intrinsic.IAND,
2604 IntrinsicCall.Intrinsic.IOR,
2605 IntrinsicCall.Intrinsic.IEOR):
2609 if stmt.lhs
not in stmt.rhs.children:
2615 ''' Perform validation of those global constraints that can only be
2616 done at code-generation time.
2618 :raises GenerationError: if the OMPAtomicDirective associated
2619 statement does not conform to a valid OpenMP atomic operation.
2623 f
"Atomic directives must always have one and only one"
2624 f
" associated statement, but found: '{self.debug_string()}'")
2628 f
"Statement '{self.children[0].debug_string()}' is not a "
2629 f
"valid OpenMP Atomic statement.")
2634 OpenMP directive to inform that the associated loop can be vectorised.
2639 :returns: the opening string statement of this directive.
2647 :returns: the ending string statement of this directive.
2651 return "omp end simd"
2654 ''' Perform validation of those global constraints that can only be
2655 done at code-generation time.
2657 :raises GenerationError: if the OMPSimdDirective does not contain
2662 not isinstance(self.
dir_bodydir_body[0], Loop)):
2664 f
"The OMP SIMD directives must always have one and only one"
2665 f
" associated loop, but found: '{self.debug_string()}'")
2669 __all__ = [
"OMPRegionDirective",
"OMPParallelDirective",
"OMPSingleDirective",
2670 "OMPMasterDirective",
"OMPDoDirective",
"OMPParallelDoDirective",
2671 "OMPSerialDirective",
"OMPTaskloopDirective",
"OMPTargetDirective",
2672 "OMPTaskwaitDirective",
"OMPDirective",
"OMPStandaloneDirective",
2673 "OMPLoopDirective",
"OMPDeclareTargetDirective",
2674 "OMPAtomicDirective",
"OMPSimdDirective"]
def gen_post_region_code(self, parent)
def children(self, my_children)
def addchild(self, child, index=None)
def reductions(self, reprod=None)
def coloured_name(self, colour=True)
def reference_accesses(self, var_accesses)
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)
def is_valid_atomic_statement(stmt)
def validate_global_constraints(self)
def gen_code(self, parent)
def validate_global_constraints(self)
def validate_global_constraints(self)
def _reduction_string(self)
def _validate_collapse_value(self)
def node_str(self, colour=True)
def omp_schedule(self, value)
def _validate_single_loop(self)
def collapse(self, value)
def gen_code(self, parent)
def node_str(self, colour=True)
def collapse(self, value)
def validate_global_constraints(self)
def gen_code(self, parent)
def lower_to_language_level(self)
def gen_code(self, parent)
def _encloses_omp_directive(self)
def infer_sharing_attributes(self)
def create(children=None)
def validate_global_constraints(self)
def validate_global_constraints(self)
def gen_code(self, parent)
def lower_to_language_level(self)
def _get_reductions_list(self, reduction_type)
def validate_global_constraints(self)
def lower_to_language_level(self)
def _check_dependency_pairing_valid(self, node1, node2, task1, task2)
def _compute_accesses_get_start_stop_step(self, preceding_nodes, task, symbol)
def _check_valid_overlap(self, sympy_ref1s, sympy_ref2s)
def _valid_dependence_literals(self, lit1, lit2)
def _valid_dependence_ref_binop(self, ref1, ref2, task1, task2)
def _validate_task_dependencies(self)
def _compute_accesses(self, ref, preceding_nodes, task)
def _valid_dependence_ranges(self, arraymixin1, arraymixin2, index)
def validate_global_constraints(self)
def gen_code(self, parent)
def gen_code(self, parent)
def validate_global_constraints(self)
def validate_global_constraints(self)
def gen_code(self, parent)