Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | # Copyright 2009-2013, 2019 Peter A. Bigot # # SPDX-License-Identifier: Apache-2.0 # This implementation is derived from the one in # [PyXB](https://github.com/pabigot/pyxb), stripped down and modified # specifically to manage edtlib Node instances. import collections class Graph: """ Represent a directed graph with edtlib Node objects as nodes. This is used to determine order dependencies among nodes in a devicetree. An edge from C{source} to C{target} indicates that some aspect of C{source} requires that some aspect of C{target} already be available. """ def __init__(self, root=None): self.__roots = None if root is not None: self.__roots = {root} self.__edge_map = collections.defaultdict(set) self.__reverse_map = collections.defaultdict(set) self.__nodes = set() def add_edge(self, source, target): """ Add a directed edge from the C{source} to the C{target}. The nodes are added to the graph if necessary. """ self.__edge_map[source].add(target) if source != target: self.__reverse_map[target].add(source) self.__nodes.add(source) self.__nodes.add(target) def roots(self): """ Return the set of nodes calculated to be roots (i.e., those that have no incoming edges). This caches the roots calculated in a previous invocation. @rtype: C{set} """ if not self.__roots: self.__roots = set() for n in self.__nodes: if n not in self.__reverse_map: self.__roots.add(n) return self.__roots def _tarjan(self): # Execute Tarjan's algorithm on the graph. # # Tarjan's algorithm # (http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm) # computes the strongly-connected components # (http://en.wikipedia.org/wiki/Strongly_connected_component) # of the graph: i.e., the sets of nodes that form a minimal # closed set under edge transition. In essence, the loops. # We use this to detect groups of components that have a # dependency cycle, and to impose a total order on components # based on dependencies. self.__stack = [] self.__scc_order = [] self.__index = 0 self.__tarjan_index = {} self.__tarjan_low_link = {} for v in self.__nodes: self.__tarjan_index[v] = None roots = sorted(self.roots(), key=node_key) if self.__nodes and not roots: raise Exception('TARJAN: No roots found in graph with {} nodes'.format(len(self.__nodes))) for r in roots: self._tarjan_root(r) # Assign ordinals for edtlib ordinal = 0 for scc in self.__scc_order: # Zephyr customization: devicetree Node graphs should have # no loops, so all SCCs should be singletons. That may # change in the future, but for now we only give an # ordinal to singletons. if len(scc) == 1: scc[0].dep_ordinal = ordinal ordinal += 1 def _tarjan_root(self, v): # Do the work of Tarjan's algorithm for a given root node. if self.__tarjan_index.get(v) is not None: # "Root" was already reached. return self.__tarjan_index[v] = self.__tarjan_low_link[v] = self.__index self.__index += 1 self.__stack.append(v) source = v for target in sorted(self.__edge_map[source], key=node_key): if self.__tarjan_index[target] is None: self._tarjan_root(target) self.__tarjan_low_link[v] = min(self.__tarjan_low_link[v], self.__tarjan_low_link[target]) elif target in self.__stack: self.__tarjan_low_link[v] = min(self.__tarjan_low_link[v], self.__tarjan_low_link[target]) if self.__tarjan_low_link[v] == self.__tarjan_index[v]: scc = [] while True: scc.append(self.__stack.pop()) if v == scc[-1]: break self.__scc_order.append(scc) def scc_order(self): """Return the strongly-connected components in order. The data structure is a list, in dependency order, of strongly connected components (which can be single nodes). Appearance of a node in a set earlier in the list indicates that it has no dependencies on any node that appears in a subsequent set. This order is preferred over a depth-first-search order for code generation, since it detects loops. """ if not self.__scc_order: self._tarjan() return self.__scc_order __scc_order = None def depends_on(self, node): """Get the nodes that 'node' directly depends on.""" return sorted(self.__edge_map[node], key=node_key) def required_by(self, node): """Get the nodes that directly depend on 'node'.""" return sorted(self.__reverse_map[node], key=node_key) def node_key(node): # This sort key ensures that sibling nodes with the same name will # use unit addresses as tiebreakers. That in turn ensures ordinals # for otherwise indistinguishable siblings are in increasing order # by unit address, which is convenient for displaying output. if node.parent: parent_path = node.parent.path else: parent_path = '/' if node.unit_addr is not None: name = node.name.rsplit('@', 1)[0] unit_addr = node.unit_addr else: name = node.name unit_addr = -1 return (parent_path, name, unit_addr) |