Reference Guide  2.5.0
dynamic_omp_task_directive.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2022-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 A. B. G. Chalk, STFC Daresbury Lab
35 # -----------------------------------------------------------------------------
36 """ This module contains the implementation of the Dynamic OpenMP Task
37 Directive node, which is used pre-lowering to represent Task Directives."""
38 
39 import itertools
40 import math
41 from collections import namedtuple
42 
43 from psyclone.errors import GenerationError, InternalError
44 from psyclone.psyir.nodes import (
45  ArrayMember,
46  ArrayOfStructuresReference,
47  ArrayReference,
48  Assignment,
49  Call,
50  IfBlock,
51  IntrinsicCall,
52  Reference,
53  StructureReference,
54 )
55 from psyclone.psyir.nodes.array_mixin import ArrayMixin
57  ArrayOfStructuresMember,
58 )
59 from psyclone.psyir.nodes.operation import BinaryOperation
60 from psyclone.psyir.nodes.loop import Loop
61 from psyclone.psyir.nodes.literal import Literal
62 from psyclone.psyir.nodes.member import Member
64  OMPPrivateClause,
65  OMPFirstprivateClause,
66  OMPDependClause,
67  OMPSharedClause,
68 )
70  OMPParallelDirective,
71 )
73  OMPTaskDirective
74 )
75 from psyclone.psyir.symbols import INTEGER_TYPE, DataSymbol
76 
77 
79  """
80  Class representing an OpenMP TASK directive in the PSyIR.
81 
82  :param list children: list of Nodes that are children of this Node.
83  :param parent: the Node in the AST that has this directive as a child
84  :type parent: :py:class:`psyclone.psyir.nodes.Node`
85  """
86 
87  _children_valid_format = (
88  "Schedule, OMPPrivateClause,"
89  "OMPFirstprivateClause, OMPSharedClause"
90  "OMPDependClause, OMPDependClause"
91  )
92 
93  # The named tuple object to handle the creation of ProxyVar
94  # elements. These contain the parent_var (The variable this
95  # is a proxy to), and the node and loop containing the parent
96  # variable. It also contains the loop with the proxy variable as
97  # its loop variable.
98  _proxy_vars = namedtuple(
99  'ProxyVars', ['parent_var', 'parent_node',
100  'loop', 'parent_loop']
101  )
102 
103  # The named tuple object containing all the clause lists that
104  # are passed throughout the functions. Each of the members
105  # of this tuple are list of References that will be added to
106  # the appropriate clause when the clauses are created.
107  _clause_lists = namedtuple(
108  'ClauseLists', ['private_list', 'firstprivate_list',
109  'shared_list', 'in_list', 'out_list']
110  )
111 
112  # List of Fortran Intrinsics allowed to appear inside a
113  # task directive.
114  _allowed_intrinsics = [
115  IntrinsicCall.Intrinsic.ABS,
116  IntrinsicCall.Intrinsic.ACOS,
117  IntrinsicCall.Intrinsic.ACOSH,
118  IntrinsicCall.Intrinsic.ASIN,
119  IntrinsicCall.Intrinsic.ASINH,
120  IntrinsicCall.Intrinsic.ATAN,
121  IntrinsicCall.Intrinsic.ATAN2,
122  IntrinsicCall.Intrinsic.ATANH,
123  IntrinsicCall.Intrinsic.CEILING,
124  IntrinsicCall.Intrinsic.COS,
125  IntrinsicCall.Intrinsic.COSH,
126  IntrinsicCall.Intrinsic.ERF,
127  IntrinsicCall.Intrinsic.EXP,
128  IntrinsicCall.Intrinsic.FLOOR,
129  IntrinsicCall.Intrinsic.INT,
130  IntrinsicCall.Intrinsic.LBOUND,
131  IntrinsicCall.Intrinsic.LEN,
132  IntrinsicCall.Intrinsic.LOG,
133  IntrinsicCall.Intrinsic.LOG10,
134  IntrinsicCall.Intrinsic.MAX,
135  IntrinsicCall.Intrinsic.MIN,
136  IntrinsicCall.Intrinsic.MODULO,
137  IntrinsicCall.Intrinsic.REAL,
138  IntrinsicCall.Intrinsic.SIGN,
139  IntrinsicCall.Intrinsic.SIN,
140  IntrinsicCall.Intrinsic.SINH,
141  IntrinsicCall.Intrinsic.SIZE,
142  IntrinsicCall.Intrinsic.SQRT,
143  IntrinsicCall.Intrinsic.TAN,
144  IntrinsicCall.Intrinsic.TANH,
145  IntrinsicCall.Intrinsic.UBOUND,
146  ]
147 
148  def __init__(self, children=None, parent=None):
149  super().__init__(
150  children=children, parent=parent
151  )
152  # We don't know if we have a parent OMPParallelClause at initialisation
153  # so we can only create dummy clauses for now.
154  self.childrenchildrenchildren.append(OMPPrivateClause())
155  self.childrenchildrenchildren.append(OMPFirstprivateClause())
156  self.childrenchildrenchildren.append(OMPSharedClause())
157  self.childrenchildrenchildren.append(
158  OMPDependClause(depend_type=OMPDependClause.DependClauseTypes.IN)
159  )
160  self.childrenchildrenchildren.append(
161  OMPDependClause(depend_type=OMPDependClause.DependClauseTypes.OUT)
162  )
163  # We store the symbol names for the parent loops so we can work out the
164  # "chunked" loop variables.
165  self._parent_loop_vars_parent_loop_vars = []
166  self._parent_loops_parent_loops = []
167  self._proxy_loop_vars_proxy_loop_vars = {}
168  self._child_loop_vars_child_loop_vars = []
169  self._parent_parallel_parent_parallel = None
170  self._parallel_private_parallel_private = None
171  self._parallel_firstprivate_parallel_firstprivate = None
172 
173  # We need to do extra steps when inside a Kern to correctly identify
174  # symbols.
175  self._in_kern_in_kern = False
176 
177  def _array_for_clause_combination_helper(self, ref, temp_list,
178  base_member=None):
179  '''
180  Helper function for creating an ArrayReference or StructureReference
181  containing an ArrayMember to place into the
182  relevant in or out dependency list when computing index combinations.
183 
184  :param ref: The reference whose symbol the created ArrayReference is
185  to reference.
186  :type ref: :py:class:`psyclone.psyir.nodes.Reference`
187  :param temp_list: The current list of indices to add to the created
188  ArrayReference
189  :type temp_list: List[:py:class:`psyclone.psyir.nodes.Node`]
190  :param base_member: Optional argument containing the array member
191  child of ref to duplicate with
192  the indices specified by temp_list
193  :type base_member: Optional[
194  :py:class:`psyclone.psyir.nodes.ArrayMember`]
195 
196  :returns: an ArrayReference to the provided symbol and with the
197  provided indices, or a StructureReference containing the
198  new ArrayMember if base_member is provided.
199  :rtype: :py:class:`psyclone.psyir.nodes.ArrayReference` or
200  :py:class:`psyclone.psyir.nodes.StructureReference`
201 
202  '''
203  final_list = [element.copy() for element in temp_list]
204  if base_member:
205  final_member = ArrayMember.create(base_member.name, final_list)
206  sref_copy = ref.copy()
207  # Copying the StructureReference includes the Members
208  members = sref_copy.walk(Member)
209  # Replace the last one with the new ArrayMember
210  members[-1].replace_with(final_member)
211  return sref_copy
212 
213  dclause = ArrayReference.create(ref.symbol, final_list)
214  return dclause
215 
216  def _add_dependencies_from_index_list(self, index_list, dependency_list,
217  reference, array_access_member=None):
218  '''
219  Computes all of the dependency combinations for an array access and
220  adds them to the provided dependency_list.
221  If array_access_member is None, then ArrayReferences will be added,
222  otherwise StructureReferences containing ArrayMembers will be added.
223 
224  :param index_list: A list of (lists of) indices to turn into
225  dependencies.
226  :type index_list: List[Union[List[
227  :py:class:`psyclone.psyir.nodes.Reference]],
228  :py:class:`psyclone.psyir.nodes.Reference]
229  :param dependency_list: The dependency list to add the newly created
230  dependencies to.
231  :type dependency_list: List[:py:class`psyclone.psyir.nodes.Reference]
232  :param reference: The reference containing the array access to create
233  new dependencies to.
234  :type reference: Union[:py:class:`psyclone.psyir.nodes.ArrayReference`,
235  :py:class:`psyclone.psyir.nodes.StructureReference`]
236  :param array_access_member: optional argument provided if reference
237  is a StructureReference to the ArrayMember
238  containing the dependencies.
239  '''
240  # So we have a list of (lists of) indices
241  # [ [index1, index4], index2, index3] so convert these
242  # to an ArrayReference again.
243  # To create all combinations, we use itertools.product
244  # We have to create a new list which only contains lists.
245  new_index_list = [element if isinstance(element, list) else [element]
246  for element in index_list]
247 
248  combinations = itertools.product(*new_index_list)
249  for temp_list in combinations:
250  new_ref = self._array_for_clause_combination_helper_array_for_clause_combination_helper(
251  reference, temp_list, array_access_member
252  )
253 
254  if new_ref not in dependency_list:
255  dependency_list.append(new_ref)
256 
257  def _find_parent_loop_vars(self):
258  """
259  Finds the loop variable of each parent loop inside the same
260  OMPParallelDirective and stores them in the _parent_loop_vars member.
261  Also stores the parent OMPParallelDirective in _parent_parallel.
262 
263  :raises GenerationError: if no ancestor OMPParallelDirective is
264  found.
265  """
266  anc = self.ancestorancestor((OMPParallelDirective, Loop))
267  while isinstance(anc, Loop):
268  # Store the loop variable of each parent loop
269  var = anc.variable
270  self._parent_loop_vars_parent_loop_vars.append(var)
271  self._parent_loops_parent_loops.append(anc)
272  # Recurse up the tree
273  anc = anc.ancestor((OMPParallelDirective, Loop))
274 
275  if not isinstance(anc, OMPParallelDirective):
276  raise GenerationError("Failed to find an ancestor "
277  "OMPParallelDirective which is required "
278  "to compute dependencies of a "
279  "(Dynamic)OMPTaskDirective.")
280 
281  # Store the parent parallel directive node
282  self._parent_parallel_parent_parallel = anc
283  self._parallel_private_parallel_private, self._parallel_firstprivate_parallel_firstprivate, _ = \
284  anc.infer_sharing_attributes()
285  self._parallel_private_parallel_private = self._parallel_private_parallel_private.union(
286  self._parallel_firstprivate_parallel_firstprivate)
287 
288  def _handle_proxy_loop_index(self, index_list, dim, index,
289  clause_lists):
290  '''
291  Handles the special case where an index is a proxy loop variable
292  to a parent of the node. In this case, we add a reference to the
293  parent's loop value to the index_list, and create a list of all
294  possible variants, as we might have multiple values set for the
295  reference, e.g. for a boundary condition if statement.
296 
297  :param index_list: The list to contain the new indices.
298  :type index_list: List[:py:class:`psyclone.psyir.nodes.Reference`]
299  :param int dim: The dimension of the index in the containing
300  ArrayReference
301  :param index: The index that is a proxy to a parent loop variable.
302  :type index: :py:class:`psyclone.psyir.nodes.Reference`
303  :param clause_lists: The namedtuple containing the lists storing the
304  clauses.
305  :type clause_lists: namedtuple(
306  private_list=List[
307  :py:class:`psyclone.psyir.nodes.Reference`
308  ],
309  firstprivate_list=List[
310  :py:class:`psyclone.psyir.nodes.Reference`
311  ],
312  shared_list=List[
313  :py:class:`psyclone.psyir.nodes.Reference`
314  ],
315  in_list=List[
316  :py:class:`psyclone.psyir.nodes.Reference`
317  ],
318  out_list=List[
319  :py:class:`psyclone.psyir.nodes.Reference`
320  ])
321 
322  '''
323  # Ensure we have the correct number of entries in index_list
324  while len(index_list) <= dim:
325  index_list.append([])
326  # For each reference our index proxies add the appropriate
327  # set of indices to the index_list
328  for temp_ref in self._proxy_loop_vars_proxy_loop_vars[index.symbol].\
329  parent_node:
330  parent_ref = temp_ref.copy()
331  if isinstance(parent_ref, BinaryOperation):
332  quick_list = []
333  self._handle_index_binop_handle_index_binop(
334  parent_ref,
335  quick_list,
336  clause_lists
337  )
338  for element in quick_list:
339  if isinstance(element, list):
340  index_list[dim].extend(element)
341  else:
342  index_list[dim].append(element)
343  else:
344  # parent_ref is a Reference, so we just append
345  # the index.
346  index_list[dim].append(parent_ref)
347 
348  def _is_reference_private(self, ref):
349  """
350  Determines whether the provided reference is private or shared in the
351  enclosing parallel region.
352 
353  :param ref: The Reference object to be determined if it is private
354  or shared.
355  :type ref: :py:class:`psyclone.psyir.nodes.Reference`
356 
357  :returns: True if ref is private, else False.
358  :rtype: bool
359  """
360  for parent_sym in self._parallel_private_parallel_private:
361  if ref.symbol.name == parent_sym.name:
362  return True
363  return False
364 
365  def _evaluate_readonly_baseref(
366  self, ref, clause_lists
367  ):
368  """
369  Evaluates any read-only References to variables inside the OpenMP task
370  region and adds a copy of the Reference to the appropriate data-sharing
371  list used to construct the clauses for this task region.
372 
373  The basic rules for this are:
374  1. If the Reference is private in the parallel region containing this
375  task, the Reference will be added to the list of firstprivate
376  References unless it has already been added to either the list of
377  private or firstprivate References for this task.
378  2. If the Reference is shared, then the Reference will be added to the
379  input list of References unless it is already present in that list.
380 
381  :param ref: The reference to be evaluated.
382  :type ref: :py:class:`psyclone.psyir.nodes.Reference`
383  :param clause_lists: The namedtuple containing the lists storing the
384  clauses.
385  :type clause_lists: namedtuple(
386  private_list=List[
387  :py:class:`psyclone.psyir.nodes.Reference`
388  ],
389  firstprivate_list=List[
390  :py:class:`psyclone.psyir.nodes.Reference`
391  ],
392  shared_list=List[
393  :py:class:`psyclone.psyir.nodes.Reference`
394  ],
395  in_list=List[
396  :py:class:`psyclone.psyir.nodes.Reference`
397  ],
398  out_list=List[
399  :py:class:`psyclone.psyir.nodes.Reference`
400  ])
401  """
402  symbol = ref.symbol
403  is_private = symbol in self._parallel_private_parallel_private
404  if is_private:
405  # If the reference is private in the parent parallel,
406  # then it is added to the firstprivate clause for this
407  # task if it has not yet been written to (i.e. is not
408  # yet in the private clause list).
409  if (ref not in clause_lists.private_list and
410  ref not in clause_lists.firstprivate_list):
411  clause_lists.firstprivate_list.append(ref.copy())
412  else:
413  # Otherwise it was a shared variable. Its not an
414  # array so we just add the name to the in_list
415  # if not already there. If its already in out_list
416  # we still add it as this is the same as an inout
417  # dependency
418  if ref not in clause_lists.in_list:
419  clause_lists.in_list.append(ref.copy())
420 
421  def _create_binops_from_step_and_divisors(self, node, ref,
422  step_val, divisor,
423  modulo, ref_index):
424  """
425  Takes a node, step_val, divisor, modulo and ref_index from
426  _handle_index_binop and computes the BinaryOperation(s)
427  required for them to be handled in a depend clause.
428 
429  :param node: the BinaryOperation being evaluated.
430  :type node: :py:class:`psyclone.psyir.nodes.BinaryOperation`
431  :param ref: the Reference representing the loop variable inside node
432  :type ref: :py:class:`psyclone.psyir.nodes.Reference`
433  :param int step_val: the step of the loop containing the node in
434  _handle_index_binop.
435  :param int divisor: the ceiling result of literal_val / step, where
436  literal_val is the int value of the Literal child
437  of the node in _handle_index_binop.
438  :param int modulo: the result of literal_val % step in
439  _handle_index_binop.
440  :param int ref_index: The index of the Reference child of node from
441  _handle_index_binop.
442 
443  :returns: the BinaryOperation(s) required to be added to the
444  index_list in _handle_index_binop to satisfy the
445  dependencies.
446  :rtype: Tuple[:py:class`psyclone.psyir.nodes.BinaryOperation`,
447  Union[:py:class`psyclone.psyir.nodes.BinaryOperation`,
448  NoneType]]
449 
450  """
451  # If the divisor is > 1, then we need to do
452  # divisor*step_val
453  # We also need to add divisor-1*step_val to cover the case
454  # where e.g. array(i+1) is inside a larger loop, as we
455  # need dependencies to array(i) and array(i+step), unless
456  # modulo == 0
457  step = None
458  step2 = None
459  if divisor > 1:
460  step = BinaryOperation.create(
461  BinaryOperation.Operator.MUL,
462  Literal(f"{divisor}", INTEGER_TYPE),
463  Literal(f"{step_val}", INTEGER_TYPE),
464  )
465  if divisor > 2:
466  step2 = BinaryOperation.create(
467  BinaryOperation.Operator.MUL,
468  Literal(f"{divisor-1}", INTEGER_TYPE),
469  Literal(f"{step_val}", INTEGER_TYPE),
470  )
471  else:
472  step2 = Literal(f"{step_val}", INTEGER_TYPE)
473  else:
474  step = Literal(f"{step_val}", INTEGER_TYPE)
475 
476  # Create a Binary Operation of the correct format.
477  binop = None
478  binop2 = None
479  if ref_index == 0:
480  # We have Ref OP Literal
481  # Setup vars to do ref OP step when we then
482  # create the BinaryOperations to represent this
483  # access
484  first_arg = ref.copy()
485  alt_first_arg = first_arg.copy()
486  second_arg = step.copy()
487  alt_second_arg = step2.copy() if step2 else None
488  else:
489  # We have Literal OP Ref
490  # Setup vars to do step OP ref when we then
491  # create the BinaryOperations to represent this
492  # access
493  first_arg = step.copy()
494  alt_first_arg = step2.copy() if step2 else None
495  second_arg = ref.copy()
496  alt_second_arg = second_arg.copy()
497  # Create the BinaryOperations for this access according to the
498  # vars we set up.
499  binop = BinaryOperation.create(
500  node.operator, first_arg, second_arg
501  )
502  # If modulo is non-zero then we need a second binop
503  # dependency to handle the modulus, so we have two
504  # dependency clauses, one which handles each of the
505  # step-sized array chunks that overlaps with this access.
506  if modulo != 0:
507  if step2 is not None:
508  binop2 = BinaryOperation.create(
509  node.operator, alt_first_arg, alt_second_arg
510  )
511  else:
512  binop2 = ref.copy()
513 
514  return binop, binop2
515 
516  def _handle_index_binop(
517  self, node, index_list, clause_lists
518  ):
519  """
520  Evaluates an expression consisting a binary operation which is used
521  to index into an array within this OpenMP task.
522 
523  For each expression, the code checks that the expression matches the
524  expected format, which is [Reference] [ADD/SUB] [Literal] (or the
525  opposite ordering). PSyclone does not currently support other binary
526  operation indexing inside an OpenMP task.
527 
528  Once this is confirmed, PSyclone builds the appropriate list of
529  References to correctly express the dependencies of this array access,
530  and appends them to the `index_list` input argument. This can depend on
531  the structure of the Loop inside the task, and any parent Loops.
532 
533  The Reference inside the binary operation must be a private or
534  firstprivate variable inside the task region, else PSyclone does not
535  support using it as an array index.
536 
537  :param node: The BinaryOperation to be evaluated.
538  :type node: :py:class:`psyclone.psyir.nodes.BinaryOperation`
539  :param index_list: A list of Nodes used to handle the dependencies
540  for this array access. This may be reused over
541  multiple calls to this function to avoid duplicating
542  Nodes.
543  :type index_list: List[:py:class:`psyclone.psyir.nodes.Node`]
544  :param clause_lists: The namedtuple containing the lists storing the
545  clauses.
546  :type clause_lists: namedtuple(
547  private_list=List[
548  :py:class:`psyclone.psyir.nodes.Reference`
549  ],
550  firstprivate_list=List[
551  :py:class:`psyclone.psyir.nodes.Reference`
552  ],
553  shared_list=List[
554  :py:class:`psyclone.psyir.nodes.Reference`
555  ],
556  in_list=List[
557  :py:class:`psyclone.psyir.nodes.Reference`
558  ],
559  out_list=List[
560  :py:class:`psyclone.psyir.nodes.Reference`
561  ])
562 
563  :raises GenerationError: if this BinaryOperation is not an addition or
564  subtraction.
565  :raises GenerationError: if this BinaryOperation does not contain both
566  a Reference and a Literal.
567  :raises GenerationError: if this BinaryOperation contains a Reference
568  to a shared variable.
569  """
570 
571  # Binary Operation operator check, must be ADD or SUB.
572  if (
573  node.operator not in [BinaryOperation.Operator.ADD,
574  BinaryOperation.Operator.SUB]
575  ):
576  raise GenerationError(
577  f"Binary Operator of type {node.operator} used "
578  f"as an array index '{node.debug_string()}' inside an "
579  f"OMPTaskDirective which is not "
580  f"supported"
581  )
582  # We have ADD or SUB BinaryOperation
583  # It must be either Ref OP Lit or Lit OP Ref
584  if not (
585  (
586  isinstance(node.children[0], Reference)
587  and isinstance(node.children[1], Literal)
588  )
589  or (
590  isinstance(node.children[0], Literal)
591  and isinstance(node.children[1], Reference)
592  )
593  ):
594  raise GenerationError(
595  f"Children of BinaryOperation are of "
596  f"types '{type(node.children[0]).__name__}' and "
597  f"'{type(node.children[1]).__name__}', expected one "
598  f"Reference and one Literal when"
599  f" used as an array index inside an "
600  f"OMPTaskDirective. The containing ArrayReference is "
601  f"'{node.parent.debug_string()}'."
602  )
603 
604  # Have Reference +/- Literal, analyse
605  # and create clause appropriately.
606 
607  # index_private stores whether the index is private.
608  index_private = False
609  # is_proxy stores whether the index is a proxy loop variable.
610  is_proxy = False
611  # ref stores the Reference child of the BinaryOperation.
612  ref = None
613  # literal stores the Literal child of the BinaryOperation.
614  literal = None
615  # ref_index stores which child the reference is from the BinOp
616  ref_index = None
617  if isinstance(node.children[0], Reference):
618  ref_index = 0
619  else:
620  ref_index = 1
621 
622  index_symbol = node.children[ref_index].symbol
623  index_private = self._is_reference_private_is_reference_private(node.children[ref_index])
624  is_proxy = index_symbol in self._proxy_loop_vars_proxy_loop_vars
625  ref = node.children[ref_index]
626  # 1-ref_index inverts 1 or 0 value so we can find the literal as well
627  literal = node.children[1-ref_index]
628 
629  # We have some array access which is of the format:
630  # array( Reference +/- Literal).
631  # If the Reference is to a proxy (is_proxy is True) then we replace
632  # the Reference with the proxy variable instead. This is the most
633  # important case.
634  # If the task contains Loops, and the Reference is to one of the
635  # Loop variables, then we create a Range object for : for that
636  # dimension, i.e. we default to assuming that the whole array
637  # range is used as to be conservative.
638  # All other situations are treated as a constant.
639 
640  # Find the child loops that are not proxy loop variables.
641  child_loop_vars = self._child_loop_vars_child_loop_vars
642 
643  # Handle the proxy_loop case
644  if is_proxy:
645  # Treat it as though we came across the parent loop variable.
646  parent_loop = self._proxy_loop_vars_proxy_loop_vars[index_symbol].parent_loop
647 
648  # Create a Reference to the real variable
649  real_ref = self._proxy_loop_vars_proxy_loop_vars[index_symbol].parent_node.copy()
650  for temp_ref in self._proxy_loop_vars_proxy_loop_vars[index_symbol].parent_node:
651  real_ref = temp_ref.copy()
652  # We have a Literal step value, and a Literal in
653  # the Binary Operation. These Literals must both be
654  # Integer types, so we will convert them to integers
655  # and do some divison.
656  step_val = int(parent_loop.step_expr.value)
657  literal_val = int(literal.value)
658  divisor = math.ceil(literal_val / step_val)
659  modulo = literal_val % step_val
660  binop, binop2 = self._create_binops_from_step_and_divisors_create_binops_from_step_and_divisors(
661  node, real_ref, step_val, divisor, modulo, ref_index
662  )
663  # Add this to the list of indexes
664  if binop2 is not None:
665  index_list.append([binop, binop2])
666  else:
667  index_list.append(binop)
668  # Proxy loop cases handled - end of "if is_proxy" statement.
669 
670  # If the variable is private:
671  elif index_private:
672  # If the variable is in our private list
673  if ref in clause_lists.private_list:
674  # If its a child loop variable
675  if ref.symbol in child_loop_vars:
676  # Return a full range (:)
677  dim = len(index_list)
678  # Find the arrayref
679  array_access_member = ref.ancestor(ArrayMember)
680  if array_access_member is not None:
681  full_range = array_access_member.get_full_range(dim)
682  else:
683  arrayref = ref.parent.parent
684  full_range = arrayref.get_full_range(dim)
685  index_list.append(full_range)
686  else:
687  # We have a private constant, written to inside
688  # our region, so we can't do anything better than
689  # a full range I think (since we don't know what
690  # the value is/how it changes.
691  # Return a full range (:)
692  dim = len(index_list)
693  arrayref = ref.parent.parent
694  full_range = arrayref.get_full_range(dim)
695  index_list.append(full_range)
696  else:
697  if ref not in clause_lists.firstprivate_list:
698  clause_lists.firstprivate_list.append(ref.copy())
699  if ref.symbol in self._parent_loop_vars_parent_loop_vars:
700  # Non-proxy access to a parent loop variable.
701  # In this case we have to do similar to when accessing a
702  # proxy loop variable.
703 
704  # Find index of parent loop var
705  ind = self._parent_loop_vars_parent_loop_vars.index(ref.symbol)
706  parent_loop = self._parent_loops_parent_loops[ind]
707  # We have a Literal step value, and a Literal in
708  # the Binary Operation. These Literals must both be
709  # Integer types, so we will convert them to integers
710  # and do some divison.
711  step_val = int(parent_loop.step_expr.value)
712  literal_val = int(literal.value)
713  divisor = math.ceil(literal_val / step_val)
714  modulo = literal_val % step_val
715  binop, binop2 = self._create_binops_from_step_and_divisors_create_binops_from_step_and_divisors(
716  node, ref, step_val, divisor,
717  modulo, ref_index
718  )
719  # Add this to the list of indexes
720  if binop2 is not None:
721  index_list.append([binop, binop2])
722  else:
723  index_list.append(binop)
724  else:
725  # It can't be a child loop variable (these have to be
726  # private). Just has to be a firstprivate constant, which
727  # we can just use the reference to for now.
728  index_list.append(node.copy())
729  else:
730  # Have a shared variable, which we're not currently supporting
731  raise GenerationError(
732  f"Shared variable '{ref.debug_string()}' used "
733  f"as an array index inside an "
734  f"OMPTaskDirective which is not "
735  f"supported. The full access is '{node.debug_string()}'."
736  )
737 
738  def _evaluate_readonly_arrayref(
739  self, ref, clause_lists
740  ):
741  """
742  Evaluates a read-only access to an Array inside the task region, and
743  computes any data-sharing clauses and dependency clauses based upon the
744  access.
745 
746  This is done by evaluating each of the array indices, and determining
747  whether they are:
748  1. A Literal index, in which case we need a dependency to that
749  specific section of the array.
750  2. A Reference index, in which case we need a dependency to the section
751  of the array represented by that Reference.
752  3. A Binary Operation, in which case the code calls
753  `_handle_index_binop` to evaluate any additional dependencies.
754 
755  Once these have been computed, any new dependencies are added into the
756  in_list, and the array reference itself will be added to the
757  shared_list if not already present.
758 
759  :param node: The Reference to be evaluated.
760  :type node: :py:class:`psyclone.psyir.nodes.Reference`
761  :param clause_lists: The namedtuple containing the lists storing the
762  clauses.
763  :type clause_lists: namedtuple(
764  private_list=List[
765  :py:class:`psyclone.psyir.nodes.Reference`
766  ],
767  firstprivate_list=List[
768  :py:class:`psyclone.psyir.nodes.Reference`
769  ],
770  shared_list=List[
771  :py:class:`psyclone.psyir.nodes.Reference`
772  ],
773  in_list=List[
774  :py:class:`psyclone.psyir.nodes.Reference`
775  ],
776  out_list=List[
777  :py:class:`psyclone.psyir.nodes.Reference`
778  ])
779 
780  :raises GenerationError: If an array index is a shared variable.
781  :raises GenerationError: If an array index is not a Reference, Literal
782  or BinaryOperation.
783  """
784  # Index list stores the set of indices to use with this ArrayMixin
785  # for the depend clause.
786  index_list = []
787 
788  # Arrays are always shared variables in the parent parallel region.
789 
790  # The reference is shared. Since it is an array,
791  # we need to check the following restrictions:
792  # 1. No ArrayReference or ArrayOfStructuresReference
793  # or StructureReference appear in the indexing.
794  # 2. Each index is a firstprivate variable, or a
795  # private parent variable that has not yet been
796  # declared (in which case we declare it as
797  # firstprivate). Alternatively each index is
798  # a BinaryOperation whose children are a
799  # Reference to a firstprivate variable and a
800  # Literal, with operator of ADD or SUB
801  for dim, index in enumerate(ref.indices):
802  # pylint: disable=unidiomatic-typecheck
803  if type(index) is Reference:
804  # Check whether the Reference is private
805  index_private = self._is_reference_private_is_reference_private(index)
806  # Check whether the reference is to a child loop variable.
807  child_loop_vars = self._child_loop_vars_child_loop_vars
808 
809  if index_private:
810  if (
811  index not in clause_lists.private_list
812  and index not in clause_lists.firstprivate_list
813  ):
814  clause_lists.firstprivate_list.append(index.copy())
815  # Special case 1. If index belongs to a child loop
816  # that is NOT a proxy for a parent loop, then we
817  # can only do as well as guessing the entire range
818  # of the loop is used.
819  if index.symbol in child_loop_vars:
820  # Append a full Range (i.e., :)
821  full_range = ref.get_full_range(dim)
822  index_list.append(full_range)
823  elif index.symbol in self._proxy_loop_vars_proxy_loop_vars:
824  # Special case 2. the index is a proxy for a parent
825  # loop's variable. In this case, we add a reference to
826  # the parent loop's value. We create a list of all
827  # possible variants, as we might have multiple values
828  # set for a value, e.g. for a boundary condition if
829  # statement.
830  self._handle_proxy_loop_index_handle_proxy_loop_index(index_list, dim, index,
831  clause_lists)
832  else:
833  # Final case is just a generic Reference, in which case
834  # just copy the Reference
835  index_list.append(index.copy())
836  else:
837  raise GenerationError(
838  f"Shared variable access used "
839  f"as an array index inside an "
840  f"OMPTaskDirective which is not "
841  f"supported. Variable name is '{index.symbol.name}'. "
842  f"The full access is '{ref.debug_string()}'."
843  )
844  elif isinstance(index, BinaryOperation):
845  # Binary Operation check
846  # A single binary operation, e.g. a(i+1) can require
847  # multiple clauses to correctly handle.
848  self._handle_index_binop_handle_index_binop(
849  index, index_list, clause_lists
850  )
851  elif isinstance(index, Literal):
852  # Just place literal directly into the dependency clause.
853  index_list.append(index.copy())
854  else:
855  # Not allowed type appears
856  raise GenerationError(
857  f"'{type(index).__name__}' object is not allowed to "
858  f"appear in an array index "
859  f"expression inside an "
860  f"OMPTaskDirective. The index was "
861  f"'{index.debug_string()}'."
862  )
863 
864  # Add all combinations of dependencies from the computed index_list
865  # into in_list
866  self._add_dependencies_from_index_list_add_dependencies_from_index_list(index_list,
867  clause_lists.in_list, ref)
868 
869  # Add to shared_list
870  sclause = Reference(ref.symbol)
871  if sclause not in clause_lists.shared_list:
872  clause_lists.shared_list.append(sclause)
873 
874  def _evaluate_structure_with_array_reference_read(
875  self,
876  ref,
877  array_access_member,
878  clause_lists,
879  ):
880  """
881  Evaluates a read-only access to an array within a structure inside the
882  task region, and computes any data-sharing clauses and dependency
883  clauses based upon the access.
884 
885  This is done by evaluating each of the array indices, and determining
886  whether they are:
887  1. A Literal index, in which case we need a dependency to that
888  specific section of the array.
889  2. A Reference index, in which case we need a dependency to the section
890  of the array represented by that Reference.
891  3. A Binary Operation, in which case the code calls
892  `_handle_index_binop` to evaluate any additional dependencies.
893 
894  Once these have been computed, any new dependencies are added into the
895  in_list, and the array reference itself will be added to the
896  shared_list if not already present.
897 
898  :param node: The Reference to be evaluated.
899  :type node: :py:class:`psyclone.psyir.nodes.Reference`
900  :param array_access_member: The ArrayMixin member child of the
901  node.
902  :type array_access_member:
903  :py:class:psyclone.psyir.nodes.array_mixin.ArrayMixin`
904  :param clause_lists: The namedtuple containing the lists storing the
905  clauses.
906  :type clause_lists: namedtuple(
907  private_list=List[
908  :py:class:`psyclone.psyir.nodes.Reference`
909  ],
910  firstprivate_list=List[
911  :py:class:`psyclone.psyir.nodes.Reference`
912  ],
913  shared_list=List[
914  :py:class:`psyclone.psyir.nodes.Reference`
915  ],
916  in_list=List[
917  :py:class:`psyclone.psyir.nodes.Reference`
918  ],
919  out_list=List[
920  :py:class:`psyclone.psyir.nodes.Reference`
921  ])
922  """
923 
924  # Index list stores the set of indices to use with this ArrayMixin
925  # for the depend clause.
926  index_list = []
927 
928  # Find the list of members we need to include in the final reference.
929  new_member = ref.member.copy()
930  sref_base = StructureReference(ref.symbol)
931  sref_base.addchild(new_member)
932  self._evaluate_structure_with_array_reference_indexlist_evaluate_structure_with_array_reference_indexlist(
933  sref_base,
934  array_access_member,
935  clause_lists,
936  index_list
937  )
938 
939  # Add all combinations of dependencies from the computed index_list
940  # into in_list
941  self._add_dependencies_from_index_list_add_dependencies_from_index_list(
942  index_list, clause_lists.in_list, sref_base,
943  array_access_member=array_access_member
944  )
945  # Add to shared_list
946  sclause = Reference(ref.symbol)
947  if sclause not in clause_lists.shared_list:
948  clause_lists.shared_list.append(sclause)
949 
950  def _evaluate_readonly_reference(
951  self, ref, clause_lists
952  ):
953  """
954  Evaluates any Reference used in a read context. This is done by
955  calling the appropriate helper functions for ArrayReferences,
956  StructureReferences or other References as appropriate.
957 
958  :param node: The Reference to be evaluated.
959  :type node: :py:class:`psyclone.psyir.nodes.Reference`
960  :param clause_lists: The namedtuple containing the lists storing the
961  clauses.
962  :type clause_lists: namedtuple(
963  private_list=List[
964  :py:class:`psyclone.psyir.nodes.Reference`
965  ],
966  firstprivate_list=List[
967  :py:class:`psyclone.psyir.nodes.Reference`
968  ],
969  shared_list=List[
970  :py:class:`psyclone.psyir.nodes.Reference`
971  ],
972  in_list=List[
973  :py:class:`psyclone.psyir.nodes.Reference`
974  ],
975  out_list=List[
976  :py:class:`psyclone.psyir.nodes.Reference`
977  ])
978 
979  :raises GenerationError: If a StructureReference containing multiple
980  ArrayMember or ArrayOfStructuresMember as
981  children is found.
982  :raises GenerationError: If an ArrayOfStructuresReference containing
983  an ArrayMember of ArrayOfStructuresMember as
984  a child is found.
985  """
986  if isinstance(ref, (ArrayReference, ArrayOfStructuresReference)):
987  # If ref is an ArrayOfStructuresReference and contains an
988  # ArrayMember then we can't handle this case.
989  if isinstance(ref, ArrayOfStructuresReference):
990  array_children = ref.walk((ArrayOfStructuresMember,
991  ArrayMember))
992  if array_children:
993  raise GenerationError(
994  f"PSyclone doesn't support an OMPTaskDirective "
995  f"containing an ArrayOfStructuresReference with "
996  f"an array accessing member. Found "
997  f"'{ref.debug_string()}'."
998  )
999 
1000  # Resolve ArrayReference or ArrayOfStructuresReference
1001  self._evaluate_readonly_arrayref_evaluate_readonly_arrayref(
1002  ref, clause_lists
1003  )
1004  elif isinstance(ref, StructureReference):
1005  # If the StructureReference contains an ArrayMixin then
1006  # we need to treat it differently, like an arrayref, however
1007  # the code to handle an arrayref is not compatible with more
1008  # than one ArrayMixin child.
1009  array_children = ref.walk((ArrayOfStructuresMember, ArrayMember))
1010  if array_children:
1011  if len(array_children) > 1:
1012  raise GenerationError(
1013  f"PSyclone doesn't support an OMPTaskDirective "
1014  f"containing a StructureReference with multiple array"
1015  f" accessing members. Found '{ref.debug_string()}'."
1016  )
1017  self._evaluate_structure_with_array_reference_read_evaluate_structure_with_array_reference_read(
1018  ref,
1019  array_children[0],
1020  clause_lists
1021  )
1022  else:
1023  # We have a StructureReference with no array children, so it
1024  # should be treated the same as a Reference, except we have to
1025  # create a Reference to the symbol as according to OpenMP
1026  # standard only accesses to the base Structure or arrays
1027  # within a structure are valid in clauses.
1028  base_ref = Reference(ref.symbol)
1029  self._evaluate_readonly_baseref_evaluate_readonly_baseref(
1030  base_ref, clause_lists
1031  )
1032  elif isinstance(ref, Reference):
1033  self._evaluate_readonly_baseref_evaluate_readonly_baseref(
1034  ref, clause_lists
1035  )
1036 
1037  def _evaluate_structure_with_array_reference_indexlist(
1038  self,
1039  sref_base,
1040  array_access_member,
1041  clause_lists,
1042  index_list
1043  ):
1044  """
1045  Evaluates an access to an array with a structure inside the task
1046  region, and generates the index_list used by the calling function -
1047  either _evaluate_structure_with_array_reference_{read/write}.
1048 
1049  This is done by evaluating each of the array indices, and determining
1050  whether they are:
1051  1. A Literal index, in which case we need a dependency to that
1052  specific section of the array.
1053  2. A Reference index, in which case we need a dependency to the section
1054  of the array represented by that Reference.
1055  3. A Binary Operation, in which case the code calls
1056  `_handle_index_binop` to evaluate any additional dependencies.
1057 
1058  Each of these results are added to the index_list, used in the callee.
1059 
1060  :param sref_base: A copy of ref containing the members included
1061  in the final reference.
1062  :type sref_base: :py:class:`psyclone.psyir.nodes.StructureReference`
1063  :param array_access_member: The ArrayMixin member child of the
1064  node.
1065  :type array_access_member:
1066  :py:class:psyclone.psyir.nodes.array_mixin.ArrayMixin`
1067  :param clause_lists: The namedtuple containing the lists storing the
1068  clauses.
1069  :type clause_lists: namedtuple(
1070  private_list=List[
1071  :py:class:`psyclone.psyir.nodes.Reference`
1072  ],
1073  firstprivate_list=List[
1074  :py:class:`psyclone.psyir.nodes.Reference`
1075  ],
1076  shared_list=List[
1077  :py:class:`psyclone.psyir.nodes.Reference`
1078  ],
1079  in_list=List[
1080  :py:class:`psyclone.psyir.nodes.Reference`
1081  ],
1082  out_list=List[
1083  :py:class:`psyclone.psyir.nodes.Reference`
1084  ])
1085  :param index_list: The output References for this task.
1086  :type index_list: List[:py:class:`psyclone.psyir.nodes.Reference`]
1087 
1088  :raises GenerationError: If an array index is a shared variable.
1089  :raises GenerationError: If an array index is not a Reference, Literal
1090  or BinaryOperation.
1091  """
1092  for dim, index in enumerate(array_access_member.indices):
1093  # pylint: disable=unidiomatic-typecheck
1094  if type(index) is Reference:
1095  # Check whether the Reference is private
1096  index_private = self._is_reference_private_is_reference_private(index)
1097  # Check whether the reference is to a child loop variable.
1098  child_loop_vars = self._child_loop_vars_child_loop_vars
1099 
1100  if index_private:
1101  if (
1102  index not in clause_lists.private_list
1103  and index not in clause_lists.firstprivate_list
1104  ):
1105  clause_lists.firstprivate_list.append(index.copy())
1106  # Special case 1. If index belongs to a child loop
1107  # that is NOT a proxy for a parent loop, then we
1108  # can only do as well as guessing the entire range
1109  # of the loop is used.
1110  if index.symbol in child_loop_vars:
1111  # Append a full Range (i.e., :)
1112  full_range = sref_base.walk(ArrayMember)[0].\
1113  get_full_range(dim)
1114  index_list.append(full_range)
1115  elif index.symbol in self._proxy_loop_vars_proxy_loop_vars:
1116  # Special case 2. the index is a proxy for a parent
1117  # loop's variable. In this case, we add a reference to
1118  # the parent loop's value. We create a list of all
1119  # possible variants, as we might have multiple values
1120  # set for a value, e.g. for a boundary condition if
1121  # statement.
1122  self._handle_proxy_loop_index_handle_proxy_loop_index(index_list, dim, index,
1123  clause_lists)
1124  else:
1125  # Final case is just a generic Reference, in which case
1126  # just copy the Reference
1127  index_list.append(index.copy())
1128  else:
1129  raise GenerationError(
1130  f"Shared variable access used "
1131  f"as an array index inside an "
1132  f"OMPTaskDirective which is not "
1133  f"supported. Variable name is '{index.symbol.name}'. "
1134  f"The full access is '{sref_base.debug_string()}'."
1135  )
1136  elif isinstance(index, BinaryOperation):
1137  # Binary Operation check
1138  # A single binary operation, e.g. a(i+1) can require
1139  # multiple clauses to correctly handle.
1140  self._handle_index_binop_handle_index_binop(
1141  index, index_list, clause_lists
1142  )
1143  elif isinstance(index, Literal):
1144  # Just place literal directly into the dependency clause.
1145  index_list.append(index.copy())
1146  else:
1147  # Not allowed type appears
1148  raise GenerationError(
1149  f"'{type(index).__name__}' object is not allowed to "
1150  f"appear in an array index "
1151  f"expression inside an "
1152  f"OMPTaskDirective."
1153  )
1154 
1155  def _evaluate_structure_with_array_reference_write(
1156  self,
1157  ref,
1158  array_access_member,
1159  clause_lists
1160  ):
1161  """
1162  Evaluates a write access to an array within a structure inside the
1163  task region, and computes any data-sharing clauses and dependency
1164  clauses based upon the access.
1165 
1166  This is done by evaluating each of the array indices, and determining
1167  whether they are:
1168  1. A Literal index, in which case we need a dependency to that
1169  specific section of the array.
1170  2. A Reference index, in which case we need a dependency to the section
1171  of the array represented by that Reference.
1172  3. A Binary Operation, in which case the code calls
1173  `_handle_index_binop` to evaluate any additional dependencies.
1174 
1175  Once these have been computed, any new dependencies are added into the
1176  out_list, and the array reference itself will be added to the
1177  shared_list if not already present.
1178 
1179  :param ref: The Reference to be evaluated.
1180  :type ref: :py:class:`psyclone.psyir.nodes.Reference`
1181  :param array_access_member: The ArrayMixin member child of the
1182  node.
1183  :type array_access_member:
1184  :py:class:psyclone.psyir.nodes.array_mixin.ArrayMixin`
1185  :param clause_lists: The namedtuple containing the lists storing the
1186  clauses.
1187  :type clause_lists: namedtuple(
1188  private_list=List[
1189  :py:class:`psyclone.psyir.nodes.Reference`
1190  ],
1191  firstprivate_list=List[
1192  :py:class:`psyclone.psyir.nodes.Reference`
1193  ],
1194  shared_list=List[
1195  :py:class:`psyclone.psyir.nodes.Reference`
1196  ],
1197  in_list=List[
1198  :py:class:`psyclone.psyir.nodes.Reference`
1199  ],
1200  out_list=List[
1201  :py:class:`psyclone.psyir.nodes.Reference`
1202  ])
1203  """
1204  # We write to this arrayref, so its shared and depend out on
1205  # the array.
1206 
1207  # Find the list of members we need to include in the final reference.
1208  new_member = ref.member.copy()
1209  sref_base = StructureReference(ref.symbol)
1210  sref_base.addchild(new_member)
1211 
1212  # Arrays are always shared at the moment, so we ignore the possibility
1213  # of it being private now.
1214 
1215  # Index list stores the set of indices to use with this ArrayMixin
1216  # for the depend clause.
1217  index_list = []
1218  self._evaluate_structure_with_array_reference_indexlist_evaluate_structure_with_array_reference_indexlist(
1219  sref_base,
1220  array_access_member,
1221  clause_lists,
1222  index_list,
1223  )
1224 
1225  # Add all combinations of dependencies from the computed index_list
1226  # into out_list
1227  self._add_dependencies_from_index_list_add_dependencies_from_index_list(
1228  index_list, clause_lists.out_list, sref_base,
1229  array_access_member=array_access_member
1230  )
1231  # Add to shared_list
1232  sclause = Reference(ref.symbol)
1233  if sclause not in clause_lists.shared_list:
1234  clause_lists.shared_list.append(sclause)
1235 
1236  def _evaluate_write_arrayref(
1237  self, ref, clause_lists
1238  ):
1239  """
1240  Evaluates a write access to an Array inside the task region, and
1241  computes any data-sharing clauses and dependency clauses based upon the
1242  access.
1243 
1244  This is done by evaluating each of the array indices, and determining
1245  whether they are:
1246  1. A Literal index, in which case we need a dependency to that
1247  specific section of the array.
1248  2. A Reference index, in which case we need a dependency to the section
1249  of the array represented by that Reference.
1250  3. A Binary Operation, in which case the code calls
1251  `_handle_index_binop` to evaluate any additional dependencies.
1252 
1253  Once these have been computed, any new dependencies are added into the
1254  out_list, and the array reference itself will be added to the
1255  shared_list if not already present.
1256 
1257  :param ref: The Reference to be evaluated.
1258  :type ref: :py:class:`psyclone.psyir.nodes.Reference`
1259  :param clause_lists: The namedtuple containing the lists storing the
1260  clauses.
1261  :type clause_lists: namedtuple(
1262  private_list=List[
1263  :py:class:`psyclone.psyir.nodes.Reference`
1264  ],
1265  firstprivate_list=List[
1266  :py:class:`psyclone.psyir.nodes.Reference`
1267  ],
1268  shared_list=List[
1269  :py:class:`psyclone.psyir.nodes.Reference`
1270  ],
1271  in_list=List[
1272  :py:class:`psyclone.psyir.nodes.Reference`
1273  ],
1274  out_list=List[
1275  :py:class:`psyclone.psyir.nodes.Reference`
1276  ])
1277 
1278  :raises GenerationError: If an array index is a shared variable.
1279  """
1280  # We write to this arrayref, so its shared and depend out on
1281  # the array.
1282 
1283  # Arrays are always shared at the moment, so we ignore the possibility
1284  # of it being private now.
1285 
1286  # Index list stores the set of indices to use with this ArrayMixin
1287  # for the depend clause.
1288  index_list = []
1289  # Work out the indices needed.
1290  for dim, index in enumerate(ref.indices):
1291  if isinstance(index, Literal):
1292  # Literals are just a value, just use the value.
1293  index_list.append(index.copy())
1294  elif isinstance(index, Reference):
1295  index_private = self._is_reference_private_is_reference_private(index)
1296  # Check whether the reference is to a child loop variable.
1297  child_loop_vars = self._child_loop_vars_child_loop_vars
1298  if index_private:
1299  if (
1300  index not in clause_lists.private_list
1301  and index not in clause_lists.firstprivate_list
1302  ):
1303  clause_lists.firstprivate_list.append(index.copy())
1304  # Special case 1. If index belongs to a child loop
1305  # that is NOT a proxy for a parent loop, then we
1306  # can only do as well as guessing the entire range
1307  # of the loop is used.
1308  if index.symbol in child_loop_vars:
1309  # Return a Full Range (i.e. :)
1310  full_range = ref.walk(ArrayMixin)[0].get_full_range(
1311  dim
1312  )
1313  index_list.append(full_range)
1314  elif index.symbol in self._proxy_loop_vars_proxy_loop_vars:
1315  # Special case 2. the index is a proxy for a parent
1316  # loop's variable. In this case, we add a reference to
1317  # the parent loop's value. We create a list of all
1318  # possible variants, as we might have multiple values
1319  # set for a value, e.g. for a boundary condition if
1320  # statement.
1321  self._handle_proxy_loop_index_handle_proxy_loop_index(index_list, dim, index,
1322  clause_lists)
1323  else:
1324  # Final case is just a generic Reference, in which case
1325  # just copy the Reference if its firstprivate.
1326  if index in clause_lists.firstprivate_list:
1327  index_list.append(index.copy())
1328  else:
1329  # If its general private, then we don't know what
1330  # the value of this is at the time we evaluate the
1331  # depend clause, so we can only generate a full
1332  # range (:)
1333  full_range = ref.walk(ArrayMixin)[0].\
1334  get_full_range(dim)
1335  index_list.append(full_range)
1336  else:
1337  raise GenerationError(
1338  f"Shared variable access used "
1339  f"as an array index inside an "
1340  f"OMPTaskDirective which is not "
1341  f"supported. Variable name is '{index.symbol.name}'. "
1342  f"The full access is '{ref.debug_string()}'."
1343  )
1344  elif isinstance(index, BinaryOperation):
1345  self._handle_index_binop_handle_index_binop(
1346  index, index_list, clause_lists
1347  )
1348 
1349  # Add all combinations of dependencies from the computed index_list
1350  # into out_list
1351  self._add_dependencies_from_index_list_add_dependencies_from_index_list(
1352  index_list, clause_lists.out_list, ref
1353  )
1354  # Add to shared_list
1355  sclause = Reference(ref.symbol)
1356  if sclause not in clause_lists.shared_list:
1357  clause_lists.shared_list.append(sclause)
1358 
1359  def _evaluate_write_baseref(
1360  self, ref, clause_lists
1361  ):
1362  """
1363  Evaluates a write to a non-ArrayReference reference. If the variable
1364  is declared private in the parent parallel region, then the variable
1365  is added to the private clause for this task.
1366 
1367  If the variable is not private (therefore is shared), it is added to
1368  the shared and output dependence lists for this task region.
1369 
1370  :param ref: The Reference to be evaluated.
1371  :type ref: :py:class:`psyclone.psyir.nodes.Reference`
1372  :param clause_lists: The namedtuple containing the lists storing the
1373  clauses.
1374  :type clause_lists: namedtuple(
1375  private_list=List[
1376  :py:class:`psyclone.psyir.nodes.Reference`
1377  ],
1378  firstprivate_list=List[
1379  :py:class:`psyclone.psyir.nodes.Reference`
1380  ],
1381  shared_list=List[
1382  :py:class:`psyclone.psyir.nodes.Reference`
1383  ],
1384  in_list=List[
1385  :py:class:`psyclone.psyir.nodes.Reference`
1386  ],
1387  out_list=List[
1388  :py:class:`psyclone.psyir.nodes.Reference`
1389  ])
1390  """
1391  # Check if its a private variable
1392  is_private = self._is_reference_private_is_reference_private(ref)
1393  # If its private should add it to private list if not already present
1394  if is_private and ref not in clause_lists.private_list:
1395  clause_lists.private_list.append(ref.copy())
1396  # Otherwise its a shared variable
1397  if not is_private:
1398  if ref not in clause_lists.shared_list:
1399  clause_lists.shared_list.append(ref.copy())
1400  if ref not in clause_lists.out_list:
1401  clause_lists.out_list.append(ref.copy())
1402 
1403  def _evaluate_write_reference(
1404  self, ref, clause_lists
1405  ):
1406  """
1407  Evaluates a write to any Reference in the task region. This is done by
1408  calling the appropriate subfunction depending on the type of the
1409  Reference.
1410 
1411  :param ref: The Reference to be evaluated.
1412  :type ref: :py:class:`psyclone.psyir.nodes.Reference`
1413  :param clause_lists: The namedtuple containing the lists storing the
1414  clauses.
1415  :type clause_lists: namedtuple(
1416  private_list=List[
1417  :py:class:`psyclone.psyir.nodes.Reference`
1418  ],
1419  firstprivate_list=List[
1420  :py:class:`psyclone.psyir.nodes.Reference`
1421  ],
1422  shared_list=List[
1423  :py:class:`psyclone.psyir.nodes.Reference`
1424  ],
1425  in_list=List[
1426  :py:class:`psyclone.psyir.nodes.Reference`
1427  ],
1428  out_list=List[
1429  :py:class:`psyclone.psyir.nodes.Reference`
1430  ])
1431 
1432  :raises GenerationError: If a StructureReference containing multiple
1433  ArrayMember or ArrayOfStructuresMember as
1434  children is found.
1435  :raises GenerationError: If an ArrayOfStructuresReference containing
1436  an ArrayMember of ArrayOfStructuresMember as
1437  a child if found.
1438  """
1439  if isinstance(ref, (ArrayReference, ArrayOfStructuresReference)):
1440  # If ref is an ArrayOfStructuresReference and contains an
1441  # ArrayMember then we can't handle this case.
1442  if isinstance(ref, ArrayOfStructuresReference):
1443  array_children = ref.walk((ArrayOfStructuresMember,
1444  ArrayMember))
1445  if array_children:
1446  raise GenerationError(
1447  f"PSyclone doesn't support an OMPTaskDirective "
1448  f"containing an ArrayOfStructuresReference with "
1449  f"an array accessing member. Found "
1450  f"'{ref.debug_string()}'."
1451  )
1452 
1453  # Resolve ArrayReference or ArrayOfStructuresReference
1454  self._evaluate_write_arrayref_evaluate_write_arrayref(
1455  ref, clause_lists
1456  )
1457  elif isinstance(ref, StructureReference):
1458  # If the StructureReference contains an ArrayMixin then
1459  # we need to treat it differently, like an arrayref, however
1460  # the code to handle an arrayref is not compatible with more
1461  # than one ArrayMixin child
1462  array_children = ref.walk((ArrayOfStructuresMember, ArrayMember))
1463  if array_children:
1464  if len(array_children) > 1:
1465  raise GenerationError(
1466  f"PSyclone doesn't support an OMPTaskDirective "
1467  f"containing a StructureReference with multiple array"
1468  f" accessing members. Found '{ref.debug_string()}'."
1469  )
1470  self._evaluate_structure_with_array_reference_write_evaluate_structure_with_array_reference_write(
1471  ref,
1472  array_children[0],
1473  clause_lists
1474  )
1475  else:
1476  # This is treated the same as a Reference, but we create a
1477  # Reference to the symbol to handle.
1478  base_ref = Reference(ref.symbol)
1479  self._evaluate_write_baseref_evaluate_write_baseref(
1480  base_ref, clause_lists
1481  )
1482  elif isinstance(ref, Reference):
1483  self._evaluate_write_baseref_evaluate_write_baseref(
1484  ref, clause_lists
1485  )
1486  else:
1487  raise InternalError(f"PSyclone can't handle an OMPTaskDirective "
1488  f"containing an assignment with a LHS that "
1489  f"is not a Reference. Found "
1490  f"'{ref.debug_string()}'.")
1491 
1492  def _evaluate_assignment(
1493  self,
1494  node,
1495  clause_lists
1496  ):
1497  """
1498  Evaluates an Assignment node within this task region. This is done
1499  by calling the appropriate subfunction on each reference on the
1500  LHS and RHS of the Assignment.
1501 
1502  :param ref: The Assignment to be evaluated.
1503  :type ref: :py:class:`psyclone.psyir.nodes.Assignment`
1504  :param clause_lists: The namedtuple containing the lists storing the
1505  clauses.
1506  :type clause_lists: namedtuple(
1507  private_list=List[
1508  :py:class:`psyclone.psyir.nodes.Reference`
1509  ],
1510  firstprivate_list=List[
1511  :py:class:`psyclone.psyir.nodes.Reference`
1512  ],
1513  shared_list=List[
1514  :py:class:`psyclone.psyir.nodes.Reference`
1515  ],
1516  in_list=List[
1517  :py:class:`psyclone.psyir.nodes.Reference`
1518  ],
1519  out_list=List[
1520  :py:class:`psyclone.psyir.nodes.Reference`
1521  ])
1522  """
1523  lhs = node.children[0]
1524  rhs = node.children[1]
1525  # Evaluate LHS
1526  self._evaluate_write_reference_evaluate_write_reference(
1527  lhs, clause_lists
1528  )
1529 
1530  # Evaluate RHS
1531  references = rhs.walk(Reference)
1532 
1533  # If RHS involves a parent loop variable, then our lhs node is a proxy
1534  # loop variable.
1535  # This handles the case where we have a parent loop, e.g.
1536  # do i = x, y, 32
1537  # and we have code inside the task region which does
1538  # my_temp_var = i+1
1539  # array(my_temp_var) = ...
1540  # In this case, we need to have this variable as an extra copy of
1541  # the proxy parent loop variable. Additionally, there can be multiple
1542  # value set for this variable, so we need to store all possible ones
1543  # (as they could occur inside an if/else statement and both values)
1544  # be visible for each set.
1545  added = False
1546  for ref in references:
1547  if isinstance(ref.parent, ArrayMixin):
1548  continue
1549  for index, parent_var in enumerate(self._parent_loop_vars_parent_loop_vars):
1550  if ref.symbol != parent_var:
1551  continue
1552  if lhs.symbol in self._proxy_loop_vars_proxy_loop_vars:
1553  if (
1554  rhs
1555  not in self._proxy_loop_vars_proxy_loop_vars[lhs.symbol].parent_node
1556  ):
1557  self._proxy_loop_vars_proxy_loop_vars[lhs.symbol].parent_node.append(
1558  rhs.copy()
1559  )
1560  else:
1561  subdict = self._proxy_vars_proxy_vars(
1562  parent_var, [rhs.copy()], node,
1563  self._parent_loops_parent_loops[index]
1564  )
1565 
1566  self._proxy_loop_vars_proxy_loop_vars[lhs.symbol] = subdict
1567  added = True
1568  # If we find any proxy loop variable on the RHS then we stop.
1569  if added:
1570  break
1571 
1572  # If RHS involves a proxy loop variable, then our lhs node is also a
1573  # proxy to that same variable
1574  # This handles the case where we have a parent loop, e.g.
1575  # do i = x, y, 32
1576  # and we have code inside the task region which does
1577  # for j = i, i + 32, 1
1578  # my_temp_var = j+1
1579  # array(my_temp_var) = ...
1580  # In this case, we need to have this variable as an extra copy of
1581  # the proxy parent loop variable. Additionally, there can be multiple
1582  # value set for this variable, so we need to store all possible ones
1583  # (as they could occur inside an if/else statement and both values)
1584  # be visible for each set.
1585  added = False
1586  key_list = list(self._proxy_loop_vars_proxy_loop_vars.keys())
1587  for ref in references:
1588  if isinstance(ref.parent, ArrayMixin):
1589  continue
1590  for index, proxy_var in enumerate(key_list):
1591  if ref.symbol == proxy_var:
1592  if lhs.symbol in self._proxy_loop_vars_proxy_loop_vars:
1593  if (
1594  rhs
1595  not in self._proxy_loop_vars_proxy_loop_vars[lhs.symbol].
1596  parent_node
1597  ):
1598  self._proxy_loop_vars_proxy_loop_vars[lhs.symbol].parent_node.\
1599  append(rhs.copy())
1600  else:
1601  subdict = self._proxy_vars_proxy_vars(
1602  self._proxy_loop_vars_proxy_loop_vars[proxy_var].parent_var,
1603  [rhs.copy()], node, self._parent_loops_parent_loops[index]
1604  )
1605  self._parent_loops_parent_loops.append(self._parent_loops_parent_loops[index])
1606  self._proxy_loop_vars_proxy_loop_vars[lhs.symbol] = subdict
1607  added = True
1608  # If we find any proxy loop variable on the RHS then we stop.
1609  if added:
1610  break
1611 
1612  # Use the intrinsic walk function to evaluate any references inside
1613  # Intrinsics
1614  for intrinsic in node.walk(IntrinsicCall):
1615  self._evaluate_intrinsic_evaluate_intrinsic(intrinsic, clause_lists)
1616 
1617  for ref in references:
1618  # If the ref has an IntrinsicCall ancestor, it will already
1619  # have been evaluated
1620  if ref.ancestor(IntrinsicCall):
1621  continue
1622  self._evaluate_readonly_reference_evaluate_readonly_reference(
1623  ref, clause_lists
1624  )
1625 
1626  def _evaluate_loop(
1627  self,
1628  node,
1629  clause_lists
1630  ):
1631  """
1632  Evaluates a Loop node within this task Region. This is done in several
1633  steps:
1634  1. Check the loop variable, start/stop/step values, and ensure that
1635  they are valid. The loop variable must not be shared and the start,
1636  stop, and step variables are not ArrayReferences. It also detects if
1637  the loop variable is a "proxy", i.e. it represents a parent loop's
1638  variable as a chunked loop variable. Any variables that are not yet
1639  private, firstprivate or shared will be declared as firstprivate (as
1640  they are read before being accessed elsewhere).
1641  2. Loop through each of the nodes in the Loop's Schedule child, and
1642  evaluate them through the _evaluate_node call.
1643 
1644  :param node: The Loop to be evaluated.
1645  :type node: :py:class:`psyclone.psyir.nodes.Loop`
1646  :param clause_lists: The namedtuple containing the lists storing the
1647  clauses.
1648  :type clause_lists: namedtuple(
1649  private_list=List[
1650  :py:class:`psyclone.psyir.nodes.Reference`
1651  ],
1652  firstprivate_list=List[
1653  :py:class:`psyclone.psyir.nodes.Reference`
1654  ],
1655  shared_list=List[
1656  :py:class:`psyclone.psyir.nodes.Reference`
1657  ],
1658  in_list=List[
1659  :py:class:`psyclone.psyir.nodes.Reference`
1660  ],
1661  out_list=List[
1662  :py:class:`psyclone.psyir.nodes.Reference`
1663  ])
1664 
1665  :raises GenerationError: If the loop variable is a shared variable.
1666  :raises GenerationError: If the loop start, stop or step expression
1667  contains an ArrayReference.
1668  :raises GenerationError: If the loop step expression contains an
1669  IntrinsicCall.
1670  """
1671  # Look at loop bounds etc first.
1672  # Find our loop initialisation, variable and bounds
1673  loop_var = node.variable
1674  start_val = node.start_expr
1675  stop_val = node.stop_expr
1676  step_val = node.step_expr
1677 
1678  to_remove = None
1679 
1680  # Check if we have a loop of type do ii = i where i is a parent loop
1681  # variable.
1682  start_val_refs = start_val.walk(Reference)
1683  if len(start_val_refs) == 1 and isinstance(
1684  start_val_refs[0], Reference
1685  ):
1686  # Loop through the parent loop variables
1687  for index, parent_var in enumerate(self._parent_loop_vars_parent_loop_vars):
1688  # If its a parent loop variable, we need to make it a proxy
1689  # variable for now.
1690  if start_val_refs[0].symbol == parent_var:
1691  to_remove = loop_var
1692  # Store the loop and parent_var
1693  subdict = self._proxy_vars_proxy_vars(
1694  parent_var, [Reference(parent_var)], node,
1695  self._parent_loops_parent_loops[index]
1696  )
1697  self._proxy_loop_vars_proxy_loop_vars[to_remove] = subdict
1698  break
1699 
1700  remove_child_var = None
1701  if to_remove is None:
1702  # If this loop is not a proxy_loop, then it is a child_loop
1703  remove_child_var = loop_var
1704  self._child_loop_vars_child_loop_vars.append(loop_var)
1705 
1706  # Loop variable is private unless already set as firstprivate.
1707  # Throw exception if shared
1708  loop_var_ref = Reference(loop_var)
1709  if loop_var not in self._parallel_private_parallel_private:
1710  raise GenerationError(
1711  "Found shared loop variable which is "
1712  "not allowed in OpenMP Task directive. "
1713  f"Variable name is {loop_var_ref.name}"
1714  )
1715  if loop_var_ref not in clause_lists.firstprivate_list:
1716  if loop_var_ref not in clause_lists.private_list:
1717  clause_lists.private_list.append(loop_var_ref)
1718 
1719  # If we have a proxy variable, the parent loop variable has to be
1720  # firstprivate
1721  if to_remove is not None:
1722  parent_var_ref = Reference(
1723  self._proxy_loop_vars_proxy_loop_vars[to_remove].parent_var
1724  )
1725  if parent_var_ref not in clause_lists.firstprivate_list:
1726  clause_lists.firstprivate_list.append(parent_var_ref.copy())
1727 
1728  # For all non-array accesses we make them firstprivate unless they
1729  # are already declared as something else
1730  for ref in start_val_refs:
1731  if isinstance(ref, (ArrayReference, ArrayOfStructuresReference)):
1732  raise GenerationError(
1733  f"'{type(ref).__name__}' not supported in "
1734  f"the start variable of a Loop in a "
1735  f"OMPTaskDirective node. The start expression is "
1736  f"'{node.start_expr.debug_string()}'."
1737  )
1738  # Ignore references inside inquiry IntrinsicCalls (e.g. BOUNDs)
1739  icall = ref.ancestor(IntrinsicCall)
1740  if icall and icall.intrinsic.is_inquiry:
1741  continue
1742  # If we have a StructureReference, then we need to only add the
1743  # base symbol to the lists
1744  if isinstance(ref, StructureReference):
1745  ref_copy = Reference(ref.symbol)
1746  # start_val can't be written to in Fortran so if its a
1747  # structure we should make it shared
1748  # Only the base Structure is allowed to be in a depend clause
1749  # in OpenMP, see OpenMP section 2.1
1750  if ref_copy not in clause_lists.shared_list:
1751  clause_lists.shared_list.append(ref_copy.copy())
1752  if ref_copy not in clause_lists.in_list:
1753  clause_lists.in_list.append(ref_copy.copy())
1754  ref = ref_copy
1755  if (
1756  ref not in clause_lists.firstprivate_list
1757  and ref not in clause_lists.private_list
1758  and ref not in clause_lists.shared_list
1759  ):
1760  clause_lists.firstprivate_list.append(ref.copy())
1761 
1762  stop_val_refs = stop_val.walk(Reference)
1763  for ref in stop_val_refs:
1764  if isinstance(ref, (ArrayReference, ArrayOfStructuresReference)):
1765  raise GenerationError(
1766  f"'{type(ref).__name__}' not supported in "
1767  f"the stop variable of a Loop in a "
1768  f"OMPTaskDirective node. The stop expression is "
1769  f"'{node.stop_expr.debug_string()}'."
1770  )
1771  # Ignore references inside inquiry IntrinsicCalls (e.g. BOUNDs)
1772  icall = ref.ancestor(IntrinsicCall)
1773  if icall and icall.intrinsic.is_inquiry:
1774  continue
1775  # If we have a StructureReference, then we need to only add the
1776  # base symbol to the lists
1777  if isinstance(ref, StructureReference):
1778  ref_copy = Reference(ref.symbol)
1779  # stop_val can't be written to in Fortran so if its a structure
1780  # we should make it shared
1781  # Only the base Structure is allowed to be in a depend clause.
1782  if ref_copy not in clause_lists.shared_list:
1783  clause_lists.shared_list.append(ref_copy.copy())
1784  if ref_copy not in clause_lists.in_list:
1785  clause_lists.in_list.append(ref_copy.copy())
1786  ref = ref_copy
1787  if (
1788  ref not in clause_lists.firstprivate_list
1789  and ref not in clause_lists.private_list
1790  and ref not in clause_lists.shared_list
1791  ):
1792  clause_lists.firstprivate_list.append(ref.copy())
1793 
1794  step_val_refs = step_val.walk(Reference)
1795  for ref in step_val_refs:
1796  if isinstance(ref, (ArrayReference, ArrayOfStructuresReference)):
1797  raise GenerationError(
1798  f"'{type(ref).__name__}' not supported in "
1799  f"the step variable of a Loop in a "
1800  f"OMPTaskDirective node. The step expression is "
1801  f"'{node.step_expr.debug_string()}'."
1802  )
1803  # We disallow intrinsic calls inside the step value, this is
1804  # beyond the scope of the current implementation
1805  if ref.ancestor(IntrinsicCall):
1806  raise GenerationError(
1807  f"IntrinsicCall not supported in the step variable "
1808  f"of a Loop in an OMPTaskDirective node. The step "
1809  f"expression is '{node.step_expr.debug_string()}'."
1810  )
1811  # If we have a StructureReference, then we need to only add the
1812  # base symbol to the lists
1813  if isinstance(ref, StructureReference):
1814  ref_copy = Reference(ref.symbol)
1815  # stop_val can't be written to in Fortran so if its a structure
1816  # we should make it shared
1817  # Only the base Structure is allowed to be in a depend clause.
1818  if ref_copy not in clause_lists.shared_list:
1819  clause_lists.shared_list.append(ref_copy.copy())
1820  if ref_copy not in clause_lists.in_list:
1821  clause_lists.in_list.append(ref_copy.copy())
1822  ref = ref_copy
1823  if (
1824  ref not in clause_lists.firstprivate_list
1825  and ref not in clause_lists.private_list
1826  and ref not in clause_lists.shared_list
1827  ):
1828  clause_lists.firstprivate_list.append(ref.copy())
1829 
1830  # Finished handling the loop bounds now
1831 
1832  # Recurse to the children
1833  for child_node in node.children[3].children:
1834  self._evaluate_node_evaluate_node(
1835  child_node,
1836  clause_lists
1837  )
1838 
1839  # Remove any stuff added to proxy_loop_vars if needed
1840  if to_remove is not None:
1841  self._proxy_loop_vars_proxy_loop_vars.pop(to_remove)
1842  # Remove from child_loop_vars if needed
1843  if remove_child_var is not None:
1844  self._child_loop_vars_child_loop_vars.remove(remove_child_var)
1845 
1846  def _evaluate_ifblock(
1847  self,
1848  node,
1849  clause_lists
1850  ):
1851  """
1852  Evaluates an ifblock inside a task region. This is done by calling
1853  _evaluate_readonly_reference on each Reference inside the if condition,
1854  and by calling _evaluate_node on each Node inside the if_body and
1855  else_body.
1856 
1857  :param node: The IfBlock to be evaluated.
1858  :type node: :py:class:`psyclone.psyir.nodes.IfBlock`
1859  :param clause_lists: The namedtuple containing the lists storing the
1860  clauses.
1861  :type clause_lists: namedtuple(
1862  private_list=List[
1863  :py:class:`psyclone.psyir.nodes.Reference`
1864  ],
1865  firstprivate_list=List[
1866  :py:class:`psyclone.psyir.nodes.Reference`
1867  ],
1868  shared_list=List[
1869  :py:class:`psyclone.psyir.nodes.Reference`
1870  ],
1871  in_list=List[
1872  :py:class:`psyclone.psyir.nodes.Reference`
1873  ],
1874  out_list=List[
1875  :py:class:`psyclone.psyir.nodes.Reference`
1876  ])
1877  """
1878  for ref in node.condition.walk(Reference):
1879  self._evaluate_readonly_reference_evaluate_readonly_reference(
1880  ref, clause_lists
1881  )
1882 
1883  # Recurse to the children
1884  # If block
1885  for child_node in node.if_body.children:
1886  self._evaluate_node_evaluate_node(
1887  child_node,
1888  clause_lists
1889  )
1890  # Else block if present
1891  if node.else_body is not None:
1892  for child_node in node.else_body.children:
1893  self._evaluate_node_evaluate_node(
1894  child_node,
1895  clause_lists
1896  )
1897 
1898  def _evaluate_intrinsic(
1899  self,
1900  node,
1901  clause_lists
1902  ):
1903  """
1904  Evaluates an IntrinsicCall node inside the task region.
1905  The function immediately stops if the intrinsic has
1906  `is_inquiry` set to True, as no data dependency occurs
1907  on these intrinsic.
1908  All other allowed intrinsics are read-only, so the relevant
1909  helper functions are called on each argument.
1910 
1911  :param node: The IntrinsicCall to be evaluated.
1912  :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall`
1913  :param clause_lists: The namedtuple containing the lists storing the
1914  clauses.
1915  :type clause_lists: namedtuple(
1916  private_list=List[
1917  :py:class:`psyclone.psyir.nodes.Reference`
1918  ],
1919  firstprivate_list=List[
1920  :py:class:`psyclone.psyir.nodes.Reference`
1921  ],
1922  shared_list=List[
1923  :py:class:`psyclone.psyir.nodes.Reference`
1924  ],
1925  in_list=List[
1926  :py:class:`psyclone.psyir.nodes.Reference`
1927  ],
1928  out_list=List[
1929  :py:class:`psyclone.psyir.nodes.Reference`
1930  ])
1931  """
1932  # If the intrinsic is an inquiry intrinsic we don't need to
1933  # do anything.
1934  if node.intrinsic.is_inquiry:
1935  return
1936 
1937  # Otherwise, we loop through the references in the IntrinsicCall
1938  # arguments and compute any new dependencies.
1939  for arg in node.arguments:
1940  for ref in arg.walk(Reference):
1941  self._evaluate_readonly_reference_evaluate_readonly_reference(
1942  ref, clause_lists
1943  )
1944 
1945  def _evaluate_node(
1946  self,
1947  node,
1948  clause_lists
1949  ):
1950  """
1951  Evaluates a generic Node inside the task region. Calls the appropriate
1952  handler depending on whether the node is an Assignment, Loop or
1953  IfBlock.
1954 
1955  :param node: The Node to be evaluated.
1956  :type node: :py:class:`psyclone.psyir.nodes.Node`
1957  :param clause_lists: The namedtuple containing the lists storing the
1958  clauses.
1959  :type clause_lists: namedtuple(
1960  private_list=List[
1961  :py:class:`psyclone.psyir.nodes.Reference`
1962  ],
1963  firstprivate_list=List[
1964  :py:class:`psyclone.psyir.nodes.Reference`
1965  ],
1966  shared_list=List[
1967  :py:class:`psyclone.psyir.nodes.Reference`
1968  ],
1969  in_list=List[
1970  :py:class:`psyclone.psyir.nodes.Reference`
1971  ],
1972  out_list=List[
1973  :py:class:`psyclone.psyir.nodes.Reference`
1974  ])
1975  """
1976  # For the node, check if it is Loop, Assignment or IfBlock
1977  if isinstance(node, Assignment):
1978  # Resolve assignment
1979  self._evaluate_assignment_evaluate_assignment(
1980  node,
1981  clause_lists
1982  )
1983  elif isinstance(node, Loop):
1984  # Resolve loop
1985  self._evaluate_loop_evaluate_loop(
1986  node,
1987  clause_lists
1988  )
1989  elif isinstance(node, IfBlock):
1990  # Resolve IfBlock
1991  self._evaluate_ifblock_evaluate_ifblock(
1992  node,
1993  clause_lists
1994  )
1995 
1996  # All other node types are ignored as they shouldn't affect
1997  # dependency computation, as these are the only nodes that
1998  # have read or write accesses that can get to this function
1999  # as Calls are prohibited in validation, and the allowed
2000  # IntrinsicCalls will all be children of other nodes.
2001 
2002  def _compute_clauses(self):
2003  """
2004  Computes the clauses for this OMPTaskDirective.
2005 
2006  The OMPTaskDirective must have exactly 1 child, which must be a Loop.
2007  Upon confirming this, the function calls _evaluate_node to compute all
2008  data-sharing attributes and dependencies.
2009  The clauses are then built up from those, and returned.
2010 
2011  :returns: The clauses computed for this OMPTaskDirective.
2012  :rtype: List[:py:class:`psyclone.psyir.nodes.OMPPrivateClause`,
2013  :py:class:`psyclone.psyir.nodes.OMPFirstprivateClause`,
2014  :py:class:`psyclone.psyir.nodes.OMPSharedClause`,
2015  :py:class:`psyclone.psyir.nodes.OMPDependClause`,
2016  :py:class:`psyclone.psyir.nodes.OMPDependClause`]
2017 
2018  :raises GenerationError: If the OMPTaskDirective has multiple children.
2019  :raises GenerationError: If the OMPTaskDirective's child is not a Loop.
2020 
2021  """
2022 
2023  # These lists will store PSyclone nodes which are to be added to the
2024  # clauses for this OMPTaskDirective.
2025  private_list = []
2026  firstprivate_list = []
2027  shared_list = []
2028  in_list = []
2029  out_list = []
2030  clause_lists = self._clause_lists_clause_lists(private_list, firstprivate_list,
2031  shared_list, in_list, out_list)
2032 
2033  # Reset this in case we already computed clauses before but are
2034  # recomputing them (usually due to a code change or multiple outputs).
2035  self._proxy_loop_vars_proxy_loop_vars = {}
2036  self._child_loop_vars_child_loop_vars = []
2037 
2038  # Find all the parent loop variables
2039  self._find_parent_loop_vars_find_parent_loop_vars()
2040 
2041  # Find the child loop node, and check our schedule contains a single
2042  # loop for now.
2043  if len(self.childrenchildrenchildren[0].children) != 1:
2044  raise GenerationError(
2045  "OMPTaskDirective must have exactly one Loop"
2046  f" child. Found "
2047  f"{len(self.children[0].children)} "
2048  "children."
2049  )
2050  if not isinstance(self.childrenchildrenchildren[0].children[0], Loop):
2051  raise GenerationError(
2052  "OMPTaskDirective must have exactly one Loop"
2053  " child. Found "
2054  f"'{type(self.children[0].children[0])}'"
2055  )
2056  self._evaluate_node_evaluate_node(
2057  self.childrenchildrenchildren[0].children[0],
2058  clause_lists
2059  )
2060 
2061  # Make the clauses to return.
2062  # We skip references to constants as we don't need them.
2063  # Constants will never be private.
2064  private_clause = OMPPrivateClause()
2065  firstprivate_clause = OMPFirstprivateClause()
2066  for ref in private_list:
2067  # If the symbol is in the parallel_firstprivate set then
2068  # we trust the parent parallel region and make it firstprivate
2069  if ref.symbol in self._parallel_firstprivate_parallel_firstprivate:
2070  firstprivate_list.append(ref)
2071  else:
2072  private_clause.addchild(ref)
2073  for ref in firstprivate_list:
2074  firstprivate_clause.addchild(ref)
2075  shared_clause = OMPSharedClause()
2076  for ref in shared_list:
2077  shared_clause.addchild(ref)
2078 
2079  in_clause = OMPDependClause(
2080  depend_type=OMPDependClause.DependClauseTypes.IN
2081  )
2082  # For input references we need to ignore references to constant
2083  # symbols. This means we need to try to get external symbols as well
2084  for ref in in_list:
2085  if isinstance(ref.symbol, DataSymbol) and ref.symbol.is_constant:
2086  continue
2087  if (ref.symbol.is_import and
2088  ref.symbol.get_external_symbol().is_constant):
2089  continue
2090  in_clause.addchild(ref)
2091  out_clause = OMPDependClause(
2092  depend_type=OMPDependClause.DependClauseTypes.OUT
2093  )
2094  for ref in out_list:
2095  out_clause.addchild(ref)
2096 
2097  return (
2098  private_clause,
2099  firstprivate_clause,
2100  shared_clause,
2101  in_clause,
2102  out_clause,
2103  )
2104 
2106  """
2107  Lowers the structure of the PSyIR tree inside the Directive
2108  to generate the Clauses that are required for this Directive.
2109  """
2110  # pylint: disable=import-outside-toplevel
2111  from psyclone.psyGen import Kern
2112 
2113  # If we find a Kern or Call child then we abort.
2114  # Note that if the transformation is used it will have already
2115  # attempted to do this inlining.
2116  if self.walkwalk(Kern):
2117  raise GenerationError(
2118  "Attempted to lower to OMPTaskDirective "
2119  "node, but the node contains a Kern "
2120  "which must be inlined first."
2121  )
2122  # We allow a subset of IntrinsicCall nodes
2123  for child in self.walkwalk(Call):
2124  if not isinstance(child, IntrinsicCall):
2125  raise GenerationError(
2126  "Attempted to lower to OMPTaskDirective "
2127  "node, but the node contains a Call "
2128  "which must be inlined first."
2129  )
2130  # Otherwise we have an IntrinsicCall
2131  if child.intrinsic not in self._allowed_intrinsics_allowed_intrinsics:
2132  raise GenerationError(
2133  f"Attempted to lower to OMPTaskDirective "
2134  f"node, but the node contains a "
2135  f"'{child.debug_string()}' intrinsic call, which "
2136  f"is not supported."
2137  )
2138 
2139  # Create the clauses
2140  (
2141  private_clause,
2142  firstprivate_clause,
2143  shared_clause,
2144  in_clause,
2145  out_clause,
2146  ) = self._compute_clauses_compute_clauses()
2147 
2148  # Replace the children with the new children
2149  old_children = self.pop_all_childrenpop_all_children()
2150  self.addchildaddchild(old_children[0])
2151  self.addchildaddchild(private_clause)
2152  self.addchildaddchild(firstprivate_clause)
2153  self.addchildaddchild(shared_clause)
2154  self.addchildaddchild(in_clause)
2155  self.addchildaddchild(out_clause)
2156  super().lower_to_language_level()
2157 
2158  # Replace this node with an OMPTaskDirective
2159  childs = self.dir_bodydir_body.pop_all_children()
2160  clauses = self.clausesclausesclauses[:]
2161  self.pop_all_childrenpop_all_children()
2162  replacement = OMPTaskDirective(children=childs, clauses=clauses)
2163  self.replace_withreplace_with(replacement)
def _evaluate_structure_with_array_reference_read(self, ref, array_access_member, clause_lists)
def _evaluate_structure_with_array_reference_indexlist(self, sref_base, array_access_member, clause_lists, index_list)
def _create_binops_from_step_and_divisors(self, node, ref, step_val, divisor, modulo, ref_index)
def _add_dependencies_from_index_list(self, index_list, dependency_list, reference, array_access_member=None)
def _evaluate_structure_with_array_reference_write(self, ref, array_access_member, clause_lists)
def children(self, my_children)
Definition: node.py:935
def addchild(self, child, index=None)
Definition: node.py:909
def replace_with(self, node, keep_name_in_context=True)
Definition: node.py:1495
def walk(self, my_type, stop_type=None, depth=None)
Definition: node.py:1075
def ancestor(self, my_type, excluding=None, include_self=False, limit=None, shared_with=None)
Definition: node.py:1161