42 This module contains the abstract Node implementation as well as
43 ChildrenList - a custom implementation of list.
56 from termcolor
import colored
62 Returns the supplied text argument unchanged. This is a swap-in
63 replacement for when termcolor.colored is not available.
65 :param str text: text to return.
66 :param _: fake argument, only required to match interface \
67 provided by termcolor.colored.
69 :returns: the supplied text, unchanged.
75 def _graphviz_digraph_class():
77 Wrapper that returns the graphviz Digraph type if graphviz is installed
80 :returns: the graphviz Digraph type or None.
81 :rtype: :py:class:`graphviz.Digraph` or NoneType.
96 Customized list to keep track of the children nodes. It is initialised with
97 a callback function that allows the validation of the inserted children.
98 Since this is a subclass of the standard list, all operations (e.g. append,
99 insert, extend, comparisons, list arithmetic operations) are conserved and
100 make use of the validation. They also trigger an update of all ancestor
101 nodes so that action can be taken in order to keep the tree consistent when
102 necessary (e.g. to update the data-movement clauses on an OpenACC data
105 :param node: reference to the node where the list belongs.
106 :type node: :py:class:`psyclone.psyir.nodes.Node`
107 :param validation_function: callback function to the validation method.
108 :type validation_function: \
109 function(int, :py:class:`psyclone.psyir.nodes.Node`)
110 :param str validation_text: textual representation of the valid children.
113 def __init__(self, node, validation_function, validation_text):
119 def _validate_item(self, index, item):
121 Validates the provided index and item before continuing inserting the
124 :param int index: position where the item is inserted into.
125 :param item: object that needs to be validated in the given position.
126 :type item: :py:class:`psyclone.psyir.nodes.Node`
128 :raises GenerationError: if the given index and item are not valid \
129 children for this list.
132 errmsg = (f
"Item '{item.__class__.__name__}' can't be child "
134 f
"'{self._node_reference.coloured_name(False)}'.")
137 errmsg = (f
"{errmsg} "
138 f
"{self._node_reference.coloured_name(False)} is a "
139 f
"LeafNode and doesn't accept children.")
141 errmsg = (f
"{errmsg} The valid format is: "
142 f
"'{self._validation_text}'.")
146 def _check_is_orphan(self, item):
148 Checks that the provided item is an orphan (has no parent or the parent
149 was predefined in the constructor but the other end of connection has
150 not finalised until now).
152 :param item: object that needs to be validated.
153 :type item: :py:class:`psyclone.psyir.nodes.Node`
155 :raises GenerationError: if the given item is not an orphan.
156 :raises GenerationError: if the item had been provided with a parent \
157 argument on its constructor and this operation is trying to \
158 make its parent a different node.
161 if item.parent
and not item.has_constructor_parent:
163 f
"Item '{item.coloured_name(False)}' can't be added as child "
164 f
"of '{self._node_reference.coloured_name(False)}' because "
165 f
"it is not an orphan. It already has a "
166 f
"'{item.parent.coloured_name(False)}' as a parent.")
168 if item.parent
and item.has_constructor_parent:
171 f
"'{self._node_reference.coloured_name(False)}' cannot be "
172 f
"set as parent of '{item.coloured_name(False)}' because "
173 f
"its constructor predefined the parent reference to a "
174 f
"different '{item.parent.coloured_name(False)}' node.")
176 def _set_parent_link(self, node):
178 Set parent connection of the given node to this ChildrenList's node.
180 :param node: node for which the parent connection need to be updated.
181 :type node: :py:class:`psyclone.psyir.nodes.Node`
186 node._has_constructor_parent =
False
189 def _del_parent_link(node):
191 Delete parent connection of the given node.
193 :param node: node for which the parent connection need to be updated.
194 :type node: :py:class:`psyclone.psyir.nodes.Node`
201 node._has_constructor_parent =
False
204 ''' Extends list append method with children node validation.
206 :param item: item to be appened to the list.
207 :type item: :py:class:`psyclone.psyir.nodes.Node`
217 ''' Extends list __setitem__ method with children node validation.
219 :param int index: position where to insert the item.
220 :param item: item to be inserted to the list.
221 :type item: :py:class:`psyclone.psyir.nodes.Node`
232 ''' Extends list insert method with children node validation.
234 :param int index: position where to insert the item.
235 :param item: item to be inserted to the list.
236 :type item: :py:class:`psyclone.psyir.nodes.Node`
239 positiveindex = index
if index >= 0
else len(self) - index
243 for position
in range(positiveindex, len(self)):
245 super().
insert(index, item)
250 ''' Extends list extend method with children node validation.
252 :param items: list of items to be appened to the list.
253 :type items: list of :py:class:`psyclone.psyir.nodes.Node`
256 for index, item
in enumerate(items):
267 ''' Extends list __delitem__ method with children node validation.
269 :param int index: position where to insert the item.
272 positiveindex = index
if index >= 0
else len(self) - index
273 for position
in range(positiveindex + 1, len(self)):
280 ''' Extends list remove method with children node validation.
282 :param item: item to be deleted the list.
283 :type item: :py:class:`psyclone.psyir.nodes.Node`
286 for position
in range(self.index(item) + 1, len(self)):
293 ''' Extends list pop method with children node validation.
295 :param int index: position of the item that is popped out, if not \
296 given, the last element is popped out.
298 :returns: the last value or the given index value from the list.
299 :rtype: :py:class:`psyclone.psyir.nodes.Node`
302 positiveindex = index
if index >= 0
else len(self) - index
304 for position
in range(positiveindex + 1, len(self)):
307 obj = super().
pop(index)
312 ''' Extends list reverse method with children node validation. '''
313 for index, item
in enumerate(self):
321 ''' Wipes the list. '''
328 def sort(self, reverse=False, key=None):
329 '''Override the default sort() implementation as this is not supported
332 :raises NotImplementedError: it makes no sense to sort the Children of
335 raise NotImplementedError(
"Sorting the Children of a Node is not "
341 Base class for a PSyIR node.
343 :param ast: reference into the fparser2 AST corresponding to this node.
344 :type ast: sub-class of :py:class:`fparser.two.Fortran2003.Base`
345 :param children: the PSyIR nodes that are children of this node.
346 :type children: list of :py:class:`psyclone.psyir.nodes.Node`
347 :param parent: that parent of this node in the PSyIR tree.
348 :type parent: :py:class:`psyclone.psyir.nodes.Node`
349 :param annotations: Tags that provide additional information about \
350 the node. The node should still be functionally correct when \
352 :type annotations: list of str
354 :raises TypeError: if a parent is supplied that is not an instance of Node.
355 :raises InternalError: if an invalid annotation tag is supplied.
367 valid_annotations = tuple()
372 _children_valid_format =
None
376 def __init__(self, ast=None, children=None, parent=None, annotations=None):
377 if parent
and not isinstance(parent, Node):
378 raise TypeError(f
"The parent of a Node must also be a Node but "
379 f
"got '{type(parent).__name__}'")
399 for annotation
in annotations:
404 f
"{self.__class__.__name__} with unrecognised "
405 f
"annotation '{annotation}', valid "
406 f
"annotations are: {self.valid_annotations}.")
412 Checks whether two nodes are equal. The basic implementation of this
413 checks whether the nodes are the same type, and whether all children
414 of the nodes are equal, and if so then
415 they are considered equal.
417 :param object other: the object to check equality to.
419 :returns: whether other is equal to self.
423 is_eq = type(self)
is type(other)
424 is_eq = is_eq
and (len(self.
childrenchildrenchildren) == len(other.children))
426 is_eq = is_eq
and child == other.children[index]
431 def _validate_child(position, child):
433 Decides whether a given child and position are valid for this node.
434 The generic implementation always returns False, this simplifies the
435 specializations as Leaf nodes will have by default the expected
436 behaviour, and non-leaf nodes need to modify this method to its
437 particular constraints anyway. Issue #765 explores if this method
438 can be auto-generated using the _children_valid_format string.
440 :param int position: the position to be validated.
441 :param child: a child to be validated.
442 :type child: :py:class:`psyclone.psyir.nodes.Node`
444 :return: whether the given child and position are valid for this node.
455 Returns the display name of this Node, optionally with colour control
456 codes (requires that the termcolor package be installed).
458 :param bool colour: whether or not to include colour control codes \
461 :returns: the name of this node, optionally with colour control codes.
465 name_string = type(self).__name__
469 if self.
_colour_colour
is None:
470 raise NotImplementedError(
471 f
"The _colour attribute is abstract so needs to be given "
472 f
"a string value in the concrete class "
473 f
"'{type(self).__name__}'.")
475 return colored(name_string, self.
_colour_colour)
476 except KeyError
as info:
478 f
"The _colour attribute in class '{type(self).__name__}' "
479 f
"has been set to a colour ('{self._colour}') that is not "
480 f
"supported by the termcolor package.")
from info
485 :param bool colour: whether or not to include control codes for \
488 :returns: a text description of this node. Will typically be \
489 overridden by sub-class.
494 text +=
"annotations='" +
','.join(self.
annotationsannotations) +
"'"
504 :returns: a reference to that part of the fparser2 parse tree that \
505 this node represents or None.
506 :rtype: sub-class of :py:class:`fparser.two.utils.Base`
513 :returns: a reference to the last node in the fparser2 parse tree \
514 that represents a child of this PSyIR node or None.
515 :rtype: sub-class of :py:class:`fparser.two.utils.Base`
522 Set a reference to the fparser2 node associated with this Node.
524 :param ast: fparser2 node associated with this Node.
525 :type ast: :py:class:`fparser.two.utils.Base`
532 Set a reference to the last fparser2 node associated with this Node.
534 :param ast: last fparser2 node associated with this Node.
535 :type ast: :py:class:`fparser.two.utils.Base`
541 ''' Return the list of annotations attached to this Node.
543 :returns: List of anotations
548 def dag(self, file_name='dag', file_format='svg'):
550 Create a dag of this node and its children, write it to file and
551 return the graph object.
553 :param str file_name: name of the file to create.
554 :param str file_format: format of the file to create. (Must be one \
555 recognised by Graphviz.)
557 :returns: the graph object or None (if the graphviz bindings are not \
559 :rtype: :py:class:`graphviz.Digraph` or NoneType
561 :raises GenerationError: if the specified file format is not \
562 recognised by Graphviz.
565 digraph = _graphviz_digraph_class()
569 graph = digraph(format=file_format)
570 except ValueError
as err:
572 f
"'{file_format}' provided")
from err
574 graph.render(filename=file_name)
578 '''Output my node's graph (dag) information and call any
579 children. Nodes with children are represented as two vertices,
580 a start and an end. Forward dependencies are represented as
581 green edges, backward dependencies are represented as red
582 edges (but their direction is reversed so the layout looks
583 reasonable) and parent child dependencies are represented as
591 start_postfix =
"_start"
595 graph.node(self.
dag_namedag_name+start_postfix)
596 graph.node(self.
dag_namedag_name+end_postfix)
605 local_name += end_postfix
608 remote_name = remote_node.dag_name
609 if remote_node.children:
612 remote_name += start_postfix
614 graph.edge(local_name, remote_name, color=
"green")
615 elif not isinstance(self, Routine):
620 remote_name = self.
parentparent.dag_name + end_postfix
621 graph.edge(local_name, remote_name, color=
"blue")
631 local_name += start_postfix
634 remote_name = remote_node.dag_name
635 if remote_node.children:
638 remote_name += end_postfix
640 graph.edge(remote_name, local_name, color=
"red")
641 elif not isinstance(self, Routine):
646 remote_name = self.
parentparent.dag_name + start_postfix
647 graph.edge(remote_name, local_name, color=
"blue")
650 if isinstance(self, Loop):
660 '''Return the dag name for this node. This includes the name of the
661 class and the index of its relative position to the parent Routine. If
662 no parent Routine is found, the index used is the absolute position
665 :returns: the dag name for this node.
676 return self.
coloured_namecoloured_name(
False) +
"_" + str(position)
680 '''Return the list of arguments associated with this Node. The default
681 implementation assumes the Node has no directly associated
682 arguments (i.e. is not a Kern class or subclass). Arguments of
683 any of this nodes descendants are considered to be
686 for call
in self.
kernelskernels():
687 args.extend(call.args)
691 '''Returns the closest preceding Node that this Node has a direct
692 dependence with or None if there is not one. Only Nodes with
693 the same parent as self are returned. Nodes inherit their
694 descendants' dependencies. The reason for this is that for
695 correctness a node must maintain its parent if it is
696 moved. For example a halo exchange and a kernel call may have
697 a dependence between them but it is the loop body containing
698 the kernel call that the halo exchange must not move beyond
699 i.e. the loop body inherits the dependencies of the routines
703 for arg
in self.
argsargs:
704 dependent_arg = arg.backward_dependence()
707 node = dependent_arg.call
711 while node.depth > self.
depthdepth:
713 if self.
sameParentsameParent(node)
and node
is not self:
721 if dependence.position < node.position:
728 '''Returns the closest following Node that this Node has a direct
729 dependence with or None if there is not one. Only Nodes with
730 the same parent as self are returned. Nodes inherit their
731 descendants' dependencies. The reason for this is that for
732 correctness a node must maintain its parent if it is
733 moved. For example a halo exchange and a kernel call may have
734 a dependence between them but it is the loop body containing
735 the kernel call that the halo exchange must not move beyond
736 i.e. the loop body inherits the dependencies of the routines
740 for arg
in self.
argsargs:
741 dependent_arg = arg.forward_dependence()
744 node = dependent_arg.call
748 while node.depth > self.
depthdepth:
750 if self.
sameParentsameParent(node)
and node
is not self:
757 if dependence.position > node.position:
764 '''If this Node can be moved to the new_node
765 (where position determines whether it is before of after the
766 new_node) without breaking any data dependencies then return True,
767 otherwise return False.
769 :param new_node: Node to which this node should be moved.
770 :type new_node: :py:class:`psyclone.psyir.nodes.Node`
771 :param str position: either 'before' or 'after'.
773 :raises GenerationError: if new_node is not an\
774 instance of :py:class:`psyclone.psyir.nodes.Node`.
775 :raises GenerationError: if position is not 'before' or 'after'.
776 :raises GenerationError: if self and new_node do not have the same\
778 :raises GenerationError: self and new_node are the same Node.
780 :returns: whether or not the specified location is valid for this node.
786 if not isinstance(new_node, Node):
788 f
"In the psyir.nodes.Node.is_valid_location() method the "
789 f
"supplied argument is not a Node, it is a "
790 f
"'{type(new_node).__name__}'.")
793 valid_positions = [
"before",
"after"]
794 if position
not in valid_positions:
796 f
"The position argument in the psyGenNode.is_valid_location() "
797 f
"method must be one of {valid_positions} but found "
803 "In the psyir.nodes.Node.is_valid_location() method "
804 "the node and the location do not have the same parent")
807 new_position = new_node.position
815 "In the psyir.nodes.Node.is_valid_location() method, the "
816 "node and the location are the same so this transformation "
817 "would have no effect.")
825 if not prev_dep_node:
829 return prev_dep_node.position < new_position
833 if not next_dep_node:
837 return next_dep_node.position > new_position
842 Returns this Node's depth in the tree: 1 for the Schedule
843 and increasing for its descendants at each level.
844 :returns: depth of the Node in the tree
849 while node
is not None:
854 def view(self, depth=0, colour=True, indent=" ", _index=None):
855 '''Output a human readable description of the current node and all of
856 its descendents as a string.
858 :param int depth: depth of the tree hierarchy for output \
860 :param bool colour: whether to include colour coding in the \
861 output. Defaults to True.
862 :param str indent: the indent to apply as the depth \
863 increases. Defaults to 4 spaces.
864 :param int _index: the position of this node wrt its siblings \
865 or None. Defaults to None.
867 :returns: a representation of this node and its descendents.
870 :raises TypeError: if one of the arguments is the wrong type.
871 :raises ValueError: if the depth argument is negative.
878 if not isinstance(depth, int):
880 f
"depth argument should be an int but found "
881 f
"{type(depth).__name__}.")
884 f
"depth argument should be a positive integer but "
886 if not isinstance(colour, bool):
888 f
"colour argument should be a bool but found "
889 f
"{type(colour).__name__}.")
890 if not isinstance(indent, str):
892 f
"indent argument should be a str but found "
893 f
"{type(indent).__name__}.")
895 full_indent = depth*indent
896 description = self.
node_strnode_str(colour=colour)
897 if not isinstance(self.
parentparent, Schedule)
or _index
is None:
898 result = f
"{full_indent}{description}\n"
900 result = f
"{full_indent}{_index}: {description}\n"
901 children_result_list = []
902 for idx, node
in enumerate(self.
_children_children):
903 children_result_list.append(
905 depth=depth + 1, _index=idx, colour=colour, indent=indent))
906 result = result +
"".join(children_result_list)
911 Adds the supplied node as a child of this node (at position index if
912 supplied). The supplied node must not have an existing parent.
914 :param child: the node to add as a child of this one.
915 :type child: :py:class:`psyclone.psyir.nodes.Node`
916 :param index: optional position at which to insert new child. Default \
917 is to append new child to the list of existing children.
918 :type index: Optional[int]
921 if index
is not None:
922 self.
_children_children.insert(index, child)
929 :returns: the immediate children of this Node.
930 :rtype: List[:py:class:`psyclone.psyir.nodes.Node`]
936 ''' Set a new children list.
938 :param my_children: new list of children.
939 :type my_children: list
941 :raises TypeError: if the given children parameter is not a list.
943 if isinstance(my_children, list):
947 self.
_children_children.extend(my_children)
949 raise TypeError(
"The 'my_children' parameter of the node.children"
950 " setter must be a list.")
955 :returns: the parent node.
956 :rtype: :py:class:`psyclone.psyir.nodes.Node` or NoneType
963 :returns: list of sibling nodes, including self.
964 :rtype: List[:py:class:`psyclone.psyir.nodes.Node`]
966 parent = self.
parentparent
967 return [self]
if parent
is None else parent.children
972 :returns: whether the constructor has predefined a parent connection
973 but the parent's children list doesn't include this node yet.
981 Find a Node's position relative to its parent Node (starting
982 with 0 if it does not have a parent).
984 :returns: relative position of a Node to its parent
987 if self.
parentparent
is None:
989 for index, child
in enumerate(self.
parentparent.children):
996 Find a Node's absolute position in the tree (starting with 0 if
997 it is the root). Needs to be computed dynamically from the
998 starting position (0) as its position may change.
1000 :returns: absolute position of a Node in the tree.
1003 :raises InternalError: if the absolute position cannot be found.
1006 if self.
rootroot
is self:
1015 def _find_position(self, children, position=None):
1017 Recurse through the tree depth first returning position of
1020 :param children: list of Nodes which are children of this Node.
1021 :type children: list of :py:class:`psyclone.psyir.nodes.Node`
1022 :param int position: start counting from this position. Defaults to \
1025 :returns: if the self has been found in the provided children list \
1026 and its relative position.
1029 :raises InternalError: if the starting position is < 0.
1032 if position
is None:
1036 f
"Search for Node position started from {position} "
1037 f
"instead of {self.START_POSITION}.")
1038 for child
in children:
1041 return True, position
1043 found, position = self.
_find_position_find_position(child.children, position)
1045 return True, position
1046 return False, position
1051 :returns: the root node of the PSyIR tree.
1052 :rtype: :py:class:`psyclone.psyir.nodes.Node`
1058 if self.
parentparent
is None:
1061 while node.parent
is not None:
1067 :returns: True if `node_2` has the same parent as this node, False \
1071 if self.
parentparent
is None or node_2.parent
is None:
1073 return self.
parentparent
is node_2.parent
1075 def walk(self, my_type, stop_type=None, depth=None):
1076 ''' Recurse through the PSyIR tree and return all objects that are
1077 an instance of 'my_type', which is either a single class or a tuple
1078 of classes. In the latter case all nodes are returned that are
1079 instances of any classes in the tuple. The recursion into the tree
1080 is stopped if an instance of 'stop_type' (which is either a single
1081 class or a tuple of classes) is found. This can be used to avoid
1082 analysing e.g. inlined kernels, or as performance optimisation to
1083 reduce the number of recursive calls. The recursion into the tree is
1084 also stopped if the (optional) 'depth' level is reached.
1086 :param my_type: the class(es) for which the instances are collected.
1087 :type my_type: type | Tuple[type, ...]
1088 :param stop_type: class(es) at which recursion is halted (optional).
1089 :type stop_type: Optional[type | Tuple[type, ...]]
1090 :param depth: the depth value the instances must have (optional).
1091 :type depth: Optional[int]
1093 :returns: list with all nodes that are instances of my_type \
1094 starting at and including this node.
1095 :rtype: List[:py:class:`psyclone.psyir.nodes.Node`]
1099 if isinstance(self, my_type)
and depth
in [
None, self.
depthdepth]:
1100 local_list.append(self)
1104 if stop_type
and isinstance(self, stop_type):
1109 if depth
is not None and self.
depthdepth >= depth:
1113 local_list += child.walk(my_type, stop_type, depth=depth)
1118 Recurse through the PSyIR tree and return lists of Nodes that are
1119 instances of 'my_type' and are immediate siblings. Here 'my_type' is
1120 either a single class or a tuple of classes. In the latter case all
1121 nodes are returned that are instances of any classes in the tuple. The
1122 recursion into the tree is stopped if an instance of 'stop_type' (which
1123 is either a single class or a tuple of classes) is found.
1125 :param my_type: the class(es) for which the instances are collected.
1126 :type my_type: type | Tuple[type, ...]
1127 :param stop_type: class(es) at which recursion is halted (optional).
1128 :type stop_type: Optional[type | Tuple[type, ...]]
1130 :returns: list of lists, each of which containing nodes that are
1131 instances of my_type and are immediate siblings, starting at
1132 and including this node.
1133 :rtype: List[List[:py:class:`psyclone.psyir.nodes.Node`]]
1138 for node
in self.
walkwalk(my_type, stop_type=stop_type):
1140 if depth
not in by_depth:
1141 by_depth[depth] = []
1142 by_depth[depth].append(node)
1146 for depth, local_list
in sorted(by_depth.items()):
1148 for node
in local_list:
1149 if not block
or node.immediately_follows(block[-1]):
1154 global_list.append(block)
1157 global_list.append(block)
1160 def ancestor(self, my_type, excluding=None, include_self=False,
1161 limit=None, shared_with=None):
1163 Search back up the tree and check whether this node has an ancestor
1164 that is an instance of the supplied type. If it does then we return
1165 it otherwise we return None. An individual (or tuple of) (sub-)
1166 class(es) to ignore may be provided via the `excluding` argument. If
1167 `include_self` is True then the current node is included in the search.
1168 If `limit` is provided then the search ceases if/when the supplied
1169 node is encountered.
1170 If `shared_with` is provided, then the ancestor search will find an
1171 ancestor of both this node and the node provided as `shared_with` if
1172 such an ancestor exists.
1174 :param my_type: class(es) to search for.
1175 :type my_type: type | Tuple[type, ...]
1176 :param excluding: (sub-)class(es) to ignore or None.
1177 :type excluding: Optional[type | Tuple[type, ...]]
1178 :param bool include_self: whether or not to include this node in the \
1180 :param limit: an optional node at which to stop the search.
1181 :type limit: Optional[:py:class:`psyclone.psyir.nodes.Node`]
1182 :param shared_with: an optional node which must also have the
1183 found node as an ancestor.
1184 :type shared_with: Optional[:py:class:`psyclone.psyir.nodes.Node`]
1186 :returns: First ancestor Node that is an instance of any of the \
1187 requested classes or None if not found.
1188 :rtype: Optional[:py:class:`psyclone.psyir.nodes.Node`]
1190 :raises TypeError: if `excluding` is provided but is not a type or \
1192 :raises TypeError: if `limit` is provided but is not an instance \
1198 myparent = self.
parentparent
1200 if excluding
is not None:
1201 if isinstance(excluding, type):
1202 excludes = (excluding, )
1203 elif isinstance(excluding, tuple):
1204 excludes = excluding
1207 f
"The 'excluding' argument to ancestor() must be a type or"
1208 f
" a tuple of types but got: '{type(excluding).__name__}'")
1210 if limit
and not isinstance(limit, Node):
1212 f
"The 'limit' argument to ancestor() must be an instance of "
1213 f
"Node but got '{type(limit).__name__}'")
1217 shared_ancestor =
None
1218 if shared_with
is not None:
1219 shared_ancestor = shared_with.ancestor(
1220 my_type, excluding=excluding,
1221 include_self=include_self, limit=limit)
1223 while myparent
is not None:
1224 if isinstance(myparent, my_type):
1225 if not (excluding
and isinstance(myparent, excludes)):
1230 if shared_ancestor
and shared_ancestor
is not myparent:
1235 if myparent
is limit:
1237 myparent = myparent.parent
1241 while (myparent
is not shared_ancestor
and myparent
and
1243 if myparent
is limit:
1245 if myparent.depth > shared_ancestor.depth:
1249 myparent = myparent.ancestor(
1250 my_type, excluding=excluding,
1251 include_self=
False, limit=limit)
1256 shared_ancestor = shared_ancestor.ancestor(
1257 my_type, excluding=excluding, include_self=
False,
1260 if myparent
is shared_ancestor:
1268 :returns: all kernels that are descendants of this node in the PSyIR.
1269 :rtype: List[:py:class:`psyclone.psyGen.Kern`]
1273 return self.
walkwalk(Kern)
1276 '''Return all :py:class:`psyclone.psyir.nodes.Node` nodes after this
1277 node. Ordering is depth first. If the `routine` argument is
1278 set to `True` then nodes are only returned if they are
1279 descendents of this node's closest ancestor routine if one
1282 :param bool routine: an optional (default `True`) argument \
1283 that only returns nodes that are within this node's \
1284 closest ancestor Routine node if one exists.
1286 :returns: a list of nodes.
1287 :rtype: :func:`list` of :py:class:`psyclone.psyir.nodes.Node`
1290 root = self.
rootroot
1297 routine_node = self.
ancestorancestor(Routine)
1300 all_nodes = root.walk(Node)
1302 for index, node
in enumerate(all_nodes):
1307 return all_nodes[position+1:]
1310 '''Return all :py:class:`psyclone.psyir.nodes.Node` nodes before this
1311 node. Ordering is depth first. If the `reverse` argument is
1312 set to `True` then the node ordering is reversed
1313 i.e. returning the nodes closest to this node first. if the
1314 `routine` argument is set to `True` then nodes are only
1315 returned if they are descendents of this node's closest
1316 ancestor routine if one exists.
1318 :param bool reverse: an optional (default `False`) argument \
1319 that reverses the order of any returned nodes (i.e. makes \
1320 them 'closest first' if set to true.
1321 :param bool routine: an optional (default `True`) argument \
1322 that only returns nodes that are within this node's \
1323 closest ancestor Routine node if one exists.
1325 :returns: a list of nodes.
1326 :rtype: :func:`list` of :py:class:`psyclone.psyir.nodes.Node`
1329 root = self.
rootroot
1336 routine_node = self.
ancestorancestor(Routine)
1339 all_nodes = root.walk(Node)
1341 for index, node
in enumerate(all_nodes):
1345 nodes = all_nodes[:position]
1352 :returns: True if this node immediately precedes `node_2`, False
1358 and self
in node_2.preceding()
1364 :returns: True if this node immediately follows `node_1`, False
1370 and self
in node_1.following()
1376 Returns a list of all of the user-supplied kernels (as opposed to
1377 builtins) that are beneath this node in the PSyIR.
1379 :returns: all user-supplied kernel calls below this node.
1380 :rtype: List[:py:class:`psyclone.psyGen.CodedKern`]
1385 return self.
walkwalk(CodedKern)
1389 :returns: all loops currently in this schedule.
1390 :rtype: List[:py:class:`psyclone.psyir.nodes.Loop`]
1394 return self.
walkwalk(Loop)
1398 Return all kernels that have reductions and are decendents of this
1399 node. If reprod is not provided, all reductions are
1400 returned. If reprod is False, all builtin reductions that are
1401 not set to reproducible are returned. If reprod is True, all
1402 builtins that are set to reproducible are returned.
1404 :param reprod: if provided, filter reductions by whether or not they \
1405 are set to be reproducible.
1406 :type reprod: Optional[bool]
1408 :returns: all kernels involving reductions that are descendants of \
1410 :rtype: List[:py:class:`psyclone.psyir.nodes.Kern`]
1416 call_reduction_list = []
1417 for call
in self.
walkwalk(Kern):
1418 if call.is_reduction:
1420 call_reduction_list.append(call)
1422 if call.reprod_reduction:
1423 call_reduction_list.append(call)
1425 if not call.reprod_reduction:
1426 call_reduction_list.append(call)
1427 return call_reduction_list
1431 :returns: True if this Node is within an OpenMP parallel region, \
1438 omp_dir = self.
ancestorancestor(OMPParallelDirective)
1445 In-place replacement of high-level concepts into generic language
1446 PSyIR constructs. This generic implementation only recurses down
1447 to its children, but this method must be re-implemented by Nodes
1448 that represent high-level concepts.
1450 :returns: the lowered version of this node.
1451 :rtype: :py:class:`psyclone.psyir.node.Node`
1458 child.lower_to_language_level()
1462 '''Get all variable access information. The default implementation
1463 just recurses down to all children.
1465 :param var_accesses: Stores the output results.
1466 :type var_accesses: \
1467 :py:class:`psyclone.core.VariablesAccessInfo`
1470 child.reference_accesses(var_accesses)
1474 ''' Some nodes (e.g. Schedule and Container) allow symbols to be
1475 scoped via an attached symbol table. This property returns the closest
1476 ScopingNode node including self.
1478 :returns: the closest ancestor ScopingNode node.
1479 :rtype: :py:class:`psyclone.psyir.node.ScopingNode`
1481 :raises SymbolError: if there is no ScopingNode ancestor.
1488 node = self.
ancestorancestor(ScopingNode, include_self=
True)
1492 f
"Unable to find the scope of node '{self}' as none of its "
1493 f
"ancestors are Container or Schedule nodes.")
1496 '''Removes self, and its descendants, from the PSyIR tree to which it
1497 is connected, and replaces it with the supplied node (and its
1500 :param node: the node that will replace self in the PSyIR \
1502 :type node: :py:class:`psyclone.psyir.nodes.node`
1503 :param bool keep_name_in_context: whether to conserve the name \
1504 referencing this node.
1506 :raises TypeError: if the argument 'node' is not a Node.
1507 :raises TypeError: if the argument 'keep_name_in_context' is not bool.
1508 :raises GenerationError: if this node does not have a parent.
1509 :raises GenerationError: if the argument 'node' has a parent.
1512 if not isinstance(node, Node):
1514 f
"The argument node in method replace_with in the Node class "
1515 f
"should be a Node but found '{type(node).__name__}'.")
1516 if not isinstance(keep_name_in_context, bool):
1518 f
"The argument keep_name_in_context in method replace_with in "
1519 f
"the Node class should be a bool but found "
1520 f
"'{type(keep_name_in_context).__name__}'.")
1521 if not self.
parentparent:
1523 "This node should have a parent if its replace_with method "
1525 if node.parent
is not None:
1527 f
"The parent of argument node in method replace_with in the "
1528 f
"Node class should be None but found "
1529 f
"'{type(node.parent).__name__}'.")
1533 if keep_name_in_context
and hasattr(self.
parentparent,
"argument_names") \
1538 self.
parentparent.replace_named_arg(name, node)
1543 ''' Remove all children of this node and return them as a list.
1545 :returns: all the children of this node as orphan nodes.
1546 :rtype: list of :py:class:`psyclone.psyir.node.Node`
1552 return free_children
1555 ''' Detach this node from the tree it belongs to. This is necessary
1556 as a precursor to inserting it as the child of another node.
1558 :returns: this node detached from its parent.
1559 :rtype: :py:class:`psyclone.psyir.node.Node`
1564 self.
parentparent.children.pop(index)
1567 def _refine_copy(self, other):
1568 ''' Refine the object attributes when a shallow copy is not the most
1569 appropriate operation during a call to the copy() method.
1571 :param other: object we are copying from.
1572 :type other: :py:class:`psyclone.psyir.node.Node`
1585 self.
childrenchildrenchildren.extend([child.copy()
for child
in other.children])
1589 ''' Return a copy of this node. This is a bespoke implementation for
1590 PSyIR nodes that will deepcopy some of its recursive data-structure
1591 (e.g. the children tree), while not copying other attributes (e.g.
1592 top-level parent reference).
1594 :returns: a copy of this node and its children.
1595 :rtype: :py:class:`psyclone.psyir.node.Node`
1599 new_instance = copy.copy(self)
1602 new_instance._refine_copy(self)
1606 ''' Validates this Node in the context of the whole PSyIR tree.
1607 Although there are validation checks for the parent<->child
1608 relationships, there are other constraints that can only be
1609 checked once the tree is complete and all transformations have
1610 been applied. (One example is that an OMP Do directive must be
1611 within the scope of an OMP Parallel directive.)
1613 By default, this routine does nothing. It must be overridden
1614 appropriately in any sub-classes to which constraints apply.
1615 If an error is found then a GenerationError should be raised.
1620 ''' Generates a Fortran-like output representation but without
1621 lowering high-level nodes. This is fast to generate because it
1622 doesn't deepcopy the tree like the Language backends and its
1623 output, although not compilable, is readable for error messages.
1625 :returns: a Fortran-like output representation of the tree.
1632 return DebugWriter()(self)
1635 ''' Generates a string with the available information about where
1636 this node has been created. It currently only works with Fortran
1637 Statements or subchildren of them.
1639 :returns: a string specifing the origin of this node.
1643 line_span =
"<unknown>"
1644 original_src =
"<unknown>"
1645 filename =
"<unknown>"
1648 node = self.
ancestorancestor(Statement, include_self=
True)
1656 if node
and node._ast:
1657 if hasattr(node._ast,
'item')
and node._ast.item:
1658 if hasattr(node._ast.item,
'reader'):
1659 if hasattr(node._ast.item.reader,
'file'):
1660 filename = node._ast.item.reader.file.name
1661 line_span = node._ast.item.span
1662 original_src = node._ast.item.line
1663 return (f
"{name} from line {line_span} of file "
1664 f
"'{filename}':\n> {original_src}")
1668 Called whenever there is a change in the PSyIR tree below this node.
1669 It is responsible for ensuring that this method does not get called
1670 recursively and then calls the _update_node() method of the current
1671 node (which is the only part that subclasses should specialise).
1672 Finally, it propagates the update signal up to the parent node
1690 def _update_node(self):
1692 Specify how this node must be updated when an update_signal is
1693 received. The modifications in this method will not trigger a
1694 recursive signal (i.e. they won't cause this node to attempt to
1695 update itself again).
1697 This base implementation does nothing.
1701 ''' Find the path in the psyir tree between ancestor and node and
1702 returns a list containing the path.
1704 The result of this method can be used to find the node from its
1705 ancestor for example by:
1707 >>> index_list = node.path_from(ancestor)
1708 >>> cursor = ancestor
1709 >>> for index in index_list:
1710 >>> cursor = cursor.children[index]
1711 >>> assert cursor is node
1713 :param ancestor: an ancestor node of self to find the path from.
1714 :type ancestor: :py:class:`psyclone.psyir.nodes.Node`
1716 :raises ValueError: if ancestor is not an ancestor of self.
1718 :returns: a list of child indices representing the path between
1724 while current_node
is not ancestor
and current_node.parent
is not None:
1725 result_list.append(current_node.position)
1726 current_node = current_node.parent
1728 if current_node
is not ancestor:
1729 raise ValueError(f
"Attempted to find path_from a non-ancestor "
1730 f
"'{type(ancestor).__name__}' node.")
1732 result_list.reverse()
1738 __all__ = [
"colored",
def _set_parent_link(self, node)
def insert(self, index, item)
def _validate_item(self, index, item)
def sort(self, reverse=False, key=None)
def __setitem__(self, index, item)
def __delitem__(self, index)
def _del_parent_link(node)
def _check_is_orphan(self, item)
def children(self, my_children)
def backward_dependence(self)
def sameParent(self, node_2)
def preceding(self, reverse=False, routine=True)
def is_valid_location(self, new_node, position="before")
def dag(self, file_name='dag', file_format='svg')
def addchild(self, child, index=None)
def lower_to_language_level(self)
def reductions(self, reprod=None)
def is_openmp_parallel(self)
def forward_dependence(self)
def immediately_follows(self, node_1)
def immediately_precedes(self, node_2)
def get_sibling_lists(self, my_type, stop_type=None)
def _validate_child(position, child)
def has_constructor_parent(self)
def replace_with(self, node, keep_name_in_context=True)
def node_str(self, colour=True)
def coloured_name(self, colour=True)
def _find_position(self, children, position=None)
def path_from(self, ancestor)
def reference_accesses(self, var_accesses)
def following(self, routine=True)
def pop_all_children(self)
def view(self, depth=0, colour=True, indent=" ", _index=None)
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)