~bzr-pqm/bzr/bzr.dev

4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
1
# Copyright (C) 2009 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17
"""Implementation of Graph algorithms when we have already loaded everything.
18
"""
19
20
cdef extern from "python-compat.h":
21
    pass
22
23
cdef extern from "Python.h":
24
    ctypedef int Py_ssize_t
25
    ctypedef struct PyObject:
26
        pass
27
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
28
    object PyFrozenSet_New(object)
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
29
    object PyTuple_New(Py_ssize_t n)
30
    void PyTuple_SET_ITEM(object t, Py_ssize_t o, object v)
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
31
    PyObject * PyTuple_GET_ITEM(object t, Py_ssize_t o)
32
    Py_ssize_t PyTuple_GET_SIZE(object t)
33
    PyObject * PyDict_GetItem(object d, object k)
34
    PyObject * PyDict_GetItem(object d, object k)
35
    Py_ssize_t PyDict_Size(object d) except -1
36
    int PyDict_CheckExact(object d)
37
    int PyDict_Next(object d, Py_ssize_t *pos, PyObject **k, PyObject **v)
38
    int PyList_Append(object l, object v) except -1
39
    PyObject * PyList_GET_ITEM(object l, Py_ssize_t o)
40
    Py_ssize_t PyList_GET_SIZE(object l)
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
41
    int PyDict_SetItem(object d, object k, object v) except -1
4371.3.23 by John Arbash Meinel
Using PySet_Add in the inner loop is a little better, not tremendously, though
42
    int PySet_Add(object s, object k) except -1
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
43
    void Py_INCREF(object)
44
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
45
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
46
import heapq
47
48
from bzrlib import revision
49
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
50
# Define these as cdef objects, so we don't have to getattr them later
51
cdef object heappush, heappop, heapify
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
52
heappush = heapq.heappush
53
heappop = heapq.heappop
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
54
heapify = heapq.heapify
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
55
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
56
57
cdef class _KnownGraphNode:
58
    """Represents a single object in the known graph."""
59
60
    cdef object key
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
61
    cdef object parents
62
    cdef object children
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
63
    cdef _KnownGraphNode linear_dominator_node
64
    cdef public long dominator_distance
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
65
    cdef public object gdfo # Int
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
66
    # This could also be simplified
67
    cdef object ancestor_of
68
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
69
    def __init__(self, key):
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
70
        cdef int i
71
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
72
        self.key = key
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
73
        self.parents = None
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
74
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
75
        self.children = []
76
        # oldest ancestor, such that no parents between here and there have >1
77
        # child or >1 parent.
78
        self.linear_dominator_node = None
79
        self.dominator_distance = 0
80
        # Greatest distance from origin
81
        self.gdfo = -1
82
        # This will become a tuple of known heads that have this node as an
83
        # ancestor
84
        self.ancestor_of = None
85
86
    property child_keys:
87
        def __get__(self):
88
            cdef _KnownGraphNode child
89
90
            keys = []
91
            for child in self.children:
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
92
                PyList_Append(keys, child.key)
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
93
            return keys
94
95
    property linear_dominator:
96
        def __get__(self):
97
            if self.linear_dominator_node is None:
98
                return None
99
            else:
100
                return self.linear_dominator_node.key
101
102
    cdef clear_references(self):
103
        self.parents = None
104
        self.children = None
105
        self.linear_dominator_node = None
106
107
    def __repr__(self):
108
        parent_keys = []
109
        for parent in self.parents:
110
            parent_keys.append(parent.key)
111
        child_keys = []
112
        for child in self.children:
113
            child_keys.append(child.key)
114
        return '%s(%s  gdfo:%s par:%s child:%s dom:%s %s)' % (
115
            self.__class__.__name__, self.key, self.gdfo,
116
            parent_keys, child_keys,
117
            self.linear_dominator, self.dominator_distance)
118
119
120
# TODO: slab allocate all _KnownGraphNode objects.
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
121
#       We already know how many we are going to need, except for a couple of
122
#       ghosts that could be allocated on demand.
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
123
124
cdef class KnownGraph:
125
    """This is a class which assumes we already know the full graph."""
126
127
    cdef public object _nodes
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
128
    cdef object _known_heads
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
129
    cdef public int do_cache
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
130
    # Nodes we've touched that we'll need to reset their info when heads() is
131
    # done
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
132
    cdef object _to_cleanup
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
133
134
    def __init__(self, parent_map, do_cache=True):
135
        """Create a new KnownGraph instance.
136
137
        :param parent_map: A dictionary mapping key => parent_keys
138
        """
139
        self._nodes = {}
140
        # Maps {sorted(revision_id, revision_id): heads}
141
        self._known_heads = {}
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
142
        self._to_cleanup = []
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
143
        self.do_cache = int(do_cache)
144
        self._initialize_nodes(parent_map)
145
        self._find_linear_dominators()
146
        self._find_gdfo()
147
148
    def __dealloc__(self):
149
        cdef _KnownGraphNode child
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
150
        cdef Py_ssize_t pos
151
        cdef PyObject *temp_node
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
152
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
153
        while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
154
            child = <_KnownGraphNode>temp_node
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
155
            child.clear_references()
156
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
157
    cdef _KnownGraphNode _get_or_create_node(self, key):
158
        cdef PyObject *temp_node
159
        cdef _KnownGraphNode node
160
161
        temp_node = PyDict_GetItem(self._nodes, key)
162
        if temp_node == NULL:
163
            node = _KnownGraphNode(key)
164
            PyDict_SetItem(self._nodes, key, node)
165
        else:
166
            node = <_KnownGraphNode>temp_node
167
        return node
168
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
169
    def _initialize_nodes(self, parent_map):
170
        """Populate self._nodes.
171
172
        After this has finished, self._nodes will have an entry for every entry
173
        in parent_map. Ghosts will have a parent_keys = None, all nodes found
174
        will also have .child_keys populated with all known child_keys.
175
        """
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
176
        cdef PyObject *temp_key, *temp_parent_keys, *temp_node
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
177
        cdef Py_ssize_t pos, pos2, num_parent_keys
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
178
        cdef _KnownGraphNode node
179
        cdef _KnownGraphNode parent_node
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
180
181
        nodes = self._nodes
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
182
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
183
        if not PyDict_CheckExact(parent_map):
184
            raise TypeError('parent_map should be a dict of {key:parent_keys}')
185
        # for key, parent_keys in parent_map.iteritems():
186
        pos = 0
187
        while PyDict_Next(parent_map, &pos, &temp_key, &temp_parent_keys):
188
            key = <object>temp_key
189
            parent_keys = <object>temp_parent_keys
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
190
            node = self._get_or_create_node(key)
191
            assert node.parents is None
192
            # We know how many parents, so we could pre allocate an exact sized
193
            # tuple here
194
            num_parent_keys = len(parent_keys)
195
            parent_nodes = PyTuple_New(num_parent_keys)
196
            # We use iter here, because parent_keys maybe be a list or tuple
197
            for pos2 from 0 <= pos2 < num_parent_keys:
198
                parent_key = parent_keys[pos2]
199
                parent_node = self._get_or_create_node(parent_keys[pos2])
200
                # PyTuple_SET_ITEM will steal a reference, so INCREF first
201
                Py_INCREF(parent_node)
202
                PyTuple_SET_ITEM(parent_nodes, pos2, parent_node)
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
203
                PyList_Append(parent_node.children, node)
204
            node.parents = parent_nodes
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
205
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
206
    cdef _KnownGraphNode _check_is_linear(self, _KnownGraphNode node):
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
207
        """Check to see if a given node is part of a linear chain."""
208
        cdef _KnownGraphNode parent_node
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
209
        if node.parents is None or PyTuple_GET_SIZE(node.parents) != 1:
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
210
            # This node is either a ghost, a tail, or has multiple parents
211
            # It its own dominator
212
            node.linear_dominator_node = node
213
            node.dominator_distance = 0
214
            return None
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
215
        parent_node = <_KnownGraphNode>PyTuple_GET_ITEM(node.parents, 0)
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
216
        if PyList_GET_SIZE(parent_node.children) > 1:
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
217
            # The parent has multiple children, so *this* node is the
218
            # dominator
219
            node.linear_dominator_node = node
220
            node.dominator_distance = 0
221
            return None
222
        # The parent is already filled in, so add and continue
223
        if parent_node.linear_dominator_node is not None:
224
            node.linear_dominator_node = parent_node.linear_dominator_node
225
            node.dominator_distance = parent_node.dominator_distance + 1
226
            return None
227
        # We don't know this node, or its parent node, so start walking to
228
        # next
229
        return parent_node
230
231
    def _find_linear_dominators(self):
232
        """For each node in the set, find any linear dominators.
233
234
        For any given node, the 'linear dominator' is an ancestor, such that
235
        all parents between this node and that one have a single parent, and a
236
        single child. So if A->B->C->D then B,C,D all have a linear dominator
237
        of A. Because there are no interesting siblings, we can quickly skip to
238
        the nearest dominator when doing comparisons.
239
        """
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
240
        cdef PyObject *temp_node
241
        cdef Py_ssize_t pos
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
242
        cdef _KnownGraphNode node
243
        cdef _KnownGraphNode next_node
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
244
        cdef _KnownGraphNode dominator
245
        cdef int i, num_elements
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
246
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
247
        pos = 0
248
        while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
249
            node = <_KnownGraphNode>temp_node
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
250
            # The parent is not filled in, so walk until we get somewhere
251
            if node.linear_dominator_node is not None: #already done
252
                continue
253
            next_node = self._check_is_linear(node)
254
            if next_node is None:
255
                # Nothing more needs to be done
256
                continue
257
            stack = []
258
            while next_node is not None:
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
259
                PyList_Append(stack, node)
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
260
                node = next_node
261
                next_node = self._check_is_linear(node)
262
            # The stack now contains the linear chain, and 'node' should have
263
            # been labeled
264
            assert node.linear_dominator_node is not None
265
            dominator = node.linear_dominator_node
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
266
            num_elements = len(stack)
267
            for i from num_elements > i >= 0:
268
                temp_node = PyList_GET_ITEM(stack, i)
269
                next_node = <_KnownGraphNode>temp_node
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
270
                next_node.linear_dominator_node = dominator
271
                next_node.dominator_distance = node.dominator_distance + 1
272
                node = next_node
273
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
274
    cdef object _find_tails(self):
275
        cdef object tails
276
        cdef PyObject *temp_node
277
        cdef Py_ssize_t pos
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
278
        cdef _KnownGraphNode node
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
279
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
280
        tails = []
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
281
        pos = 0
282
        while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
283
            node = <_KnownGraphNode>temp_node
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
284
            if node.parents is None or PyTuple_GET_SIZE(node.parents) == 0:
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
285
                PyList_Append(tails, node)
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
286
        return tails
287
288
    def _find_gdfo(self):
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
289
        cdef PyObject *temp_node
290
        cdef Py_ssize_t pos, pos2
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
291
        cdef _KnownGraphNode node
292
        cdef _KnownGraphNode child_node
293
        cdef _KnownGraphNode parent_node
294
295
        tails = self._find_tails()
296
        todo = []
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
297
        for pos from 0 <= pos < PyList_GET_SIZE(tails):
298
            temp_node = PyList_GET_ITEM(tails, pos)
299
            node = <_KnownGraphNode>temp_node
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
300
            node.gdfo = 1
301
            heappush(todo, (1, node))
302
        max_gdfo = len(self._nodes) + 1
4371.3.29 by John Arbash Meinel
A couple quick cleanups
303
        while PyList_GET_SIZE(todo) > 0:
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
304
            temp_node = PyTuple_GET_ITEM(heappop(todo), 1)
305
            node = <_KnownGraphNode>temp_node
306
            next_gdfo = node.gdfo + 1
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
307
            assert next_gdfo <= max_gdfo
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
308
            for pos from 0 <= pos < PyList_GET_SIZE(node.children):
309
                temp_node = PyList_GET_ITEM(node.children, pos)
310
                child_node = <_KnownGraphNode>temp_node
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
311
                if child_node.gdfo < next_gdfo:
312
                    # Only enque children when all of their parents have been
313
                    # resolved
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
314
                    for pos2 from 0 <= pos2 < PyTuple_GET_SIZE(child_node.parents):
315
                        temp_node = PyTuple_GET_ITEM(child_node.parents, pos2)
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
316
                        parent_node = <_KnownGraphNode>temp_node
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
317
                        # We know that 'this' parent is counted
318
                        if parent_node is not node:
319
                            if parent_node.gdfo == -1:
320
                                break
321
                    else:
322
                        child_node.gdfo = next_gdfo
323
                        heappush(todo, (next_gdfo, child_node))
324
325
    def heads(self, keys):
326
        """Return the heads from amongst keys.
327
328
        This is done by searching the ancestries of each key.  Any key that is
329
        reachable from another key is not returned; all the others are.
330
331
        This operation scales with the relative depth between any two keys. If
332
        any two keys are completely disconnected all ancestry of both sides
333
        will be retrieved.
334
335
        :param keys: An iterable of keys.
336
        :return: A set of the heads. Note that as a set there is no ordering
337
            information. Callers will need to filter their input to create
338
            order if they need it.
339
        """
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
340
        cdef PyObject *maybe_node
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
341
        cdef PyObject *maybe_heads
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
342
4371.3.24 by John Arbash Meinel
A couple of cleanups.
343
        heads_key = PyFrozenSet_New(keys)
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
344
        maybe_heads = PyDict_GetItem(self._known_heads, heads_key)
345
        if maybe_heads != NULL:
346
            return <object>maybe_heads
347
348
        # Not cached, compute it ourselves
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
349
        candidate_nodes = {}
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
350
        nodes = self._nodes
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
351
        for key in keys:
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
352
            maybe_node = PyDict_GetItem(nodes, key)
353
            if maybe_node == NULL:
354
                raise KeyError('key %s not in nodes' % (key,))
355
            PyDict_SetItem(candidate_nodes, key, <object>maybe_node)
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
356
        if revision.NULL_REVISION in candidate_nodes:
357
            # NULL_REVISION is only a head if it is the only entry
358
            candidate_nodes.pop(revision.NULL_REVISION)
359
            if not candidate_nodes:
360
                return set([revision.NULL_REVISION])
4371.3.24 by John Arbash Meinel
A couple of cleanups.
361
            # The keys changed, so recalculate heads_key
362
            heads_key = PyFrozenSet_New(candidate_nodes)
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
363
        if len(candidate_nodes) < 2:
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
364
            return heads_key
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
365
        # Check the linear dominators of these keys, to see if we already
366
        # know the heads answer
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
367
        dom_lookup_key, heads = self._heads_from_dominators(candidate_nodes)
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
368
        if heads is not None:
369
            return heads
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
370
        heads = self._heads_from_candidate_nodes(candidate_nodes)
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
371
        if self.do_cache:
372
            self._cache_heads(heads, heads_key, dom_lookup_key, candidate_nodes)
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
373
        return heads
374
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
375
    cdef object _cache_heads(self, heads, heads_key, dom_lookup_key,
376
                             candidate_nodes):
377
        cdef PyObject *maybe_node
378
        cdef _KnownGraphNode node
379
380
        PyDict_SetItem(self._known_heads, heads_key, heads)
381
        dom_heads = []
382
        for key in heads:
383
            maybe_node = PyDict_GetItem(candidate_nodes, key)
384
            if maybe_node == NULL:
385
                raise KeyError
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
386
            node = <_KnownGraphNode>maybe_node
387
            PyList_Append(dom_heads, node.linear_dominator_node.key)
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
388
        PyDict_SetItem(self._known_heads, dom_lookup_key,
389
                       PyFrozenSet_New(dom_heads))
390
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
391
    cdef object _heads_from_dominators(self, candidate_nodes):
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
392
        cdef PyObject *maybe_heads
393
        cdef PyObject *maybe_key
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
394
        cdef _KnownGraphNode node
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
395
        cdef Py_ssize_t pos
396
        cdef PyObject *temp_node
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
397
398
        dom_list_key = []
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
399
        pos = 0
400
        while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
401
            node = <_KnownGraphNode>temp_node
402
            PyList_Append(dom_list_key, node.linear_dominator_node.key)
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
403
        dom_lookup_key = PyFrozenSet_New(dom_list_key)
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
404
        maybe_heads = PyDict_GetItem(self._known_heads, dom_lookup_key)
405
        if maybe_heads == NULL:
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
406
            return dom_lookup_key, None
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
407
        # We need to map back from the dominator head to the original keys
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
408
        dom_heads = <object>maybe_heads
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
409
        dom_to_key = {}
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
410
        pos = 0
411
        while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
412
            node = <_KnownGraphNode>temp_node
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
413
            PyDict_SetItem(dom_to_key, node.linear_dominator_node.key,
414
                                       node.key)
415
        heads = []
416
        for dom_key in dom_heads:
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
417
            maybe_key = PyDict_GetItem(dom_to_key, dom_key)
418
            if maybe_key == NULL:
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
419
                # Should never happen
420
                raise KeyError
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
421
            PyList_Append(heads, <object>maybe_key)
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
422
        return dom_lookup_key, PyFrozenSet_New(heads)
423
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
424
    cdef int _process_parent(self, _KnownGraphNode node,
425
                             _KnownGraphNode parent_node,
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
426
                             candidate_nodes,
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
427
                             queue) except -1:
428
        """Process the parent of a node, seeing if we need to walk it.
429
430
        :return: 0 No extra work needed
431
                 1 This was a candidate node, and now there is only 1 candidate
432
                   left, so break out of the loop
433
        """
434
        cdef PyObject *maybe_candidate
435
        maybe_candidate = PyDict_GetItem(candidate_nodes, parent_node.key)
436
        if maybe_candidate != NULL:
437
            candidate_nodes.pop(parent_node.key)
438
            if len(candidate_nodes) <= 1:
439
                return 1
440
        if parent_node.ancestor_of is None:
441
            # This node hasn't been walked yet, so just project node's ancestor
442
            # info directly to parent_node, and enqueue it for later processing
443
            parent_node.ancestor_of = node.ancestor_of
444
            heappush(queue, (-parent_node.gdfo, parent_node))
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
445
            PyList_Append(self._to_cleanup, parent_node)
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
446
        elif parent_node.ancestor_of != node.ancestor_of:
447
            # Combine to get the full set of parents
448
            # Rewrite using PySet_* functions, unfortunately you have to use
449
            # PySet_Add since there is no PySet_Update... :(
450
            all_ancestors = set(parent_node.ancestor_of)
4371.3.23 by John Arbash Meinel
Using PySet_Add in the inner loop is a little better, not tremendously, though
451
            for k in node.ancestor_of:
452
                PySet_Add(all_ancestors, k)
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
453
            parent_node.ancestor_of = tuple(sorted(all_ancestors))
454
        return 0
455
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
456
    cdef object _heads_from_candidate_nodes(self, candidate_nodes):
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
457
        cdef _KnownGraphNode node
458
        cdef _KnownGraphNode parent_node
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
459
        cdef Py_ssize_t num_candidates
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
460
        cdef int num_parents
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
461
        cdef Py_ssize_t pos
462
        cdef PyObject *temp_node
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
463
464
        queue = []
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
465
        pos = 0
466
        while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
467
            node = <_KnownGraphNode>temp_node
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
468
            assert node.ancestor_of is None
469
            node.ancestor_of = (node.key,)
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
470
            PyList_Append(queue, (-node.gdfo, node))
471
            PyList_Append(self._to_cleanup, node)
4371.3.26 by John Arbash Meinel
A few more cleanups. Start moving away from pyrex 0.9.8isms
472
        heapify(queue)
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
473
        # These are nodes that we determined are 'common' that we are no longer
474
        # walking
475
        # Now we walk nodes until all nodes that are being walked are 'common'
476
        num_candidates = len(candidate_nodes)
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
477
        while PyList_GET_SIZE(queue) > 0 and PyDict_Size(candidate_nodes) > 1:
478
            temp_node = PyTuple_GET_ITEM(heappop(queue), 1)
479
            node = <_KnownGraphNode>temp_node
480
            if PyTuple_GET_SIZE(node.ancestor_of) == num_candidates:
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
481
                # This node is now considered 'common'
482
                # Make sure all parent nodes are marked as such
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
483
                for pos from 0 <= pos < PyTuple_GET_SIZE(node.parents):
484
                    temp_node = PyTuple_GET_ITEM(node.parents, pos)
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
485
                    parent_node = <_KnownGraphNode>temp_node
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
486
                    if parent_node.ancestor_of is not None:
487
                        parent_node.ancestor_of = node.ancestor_of
488
                if node.linear_dominator_node is not node:
489
                    parent_node = node.linear_dominator_node
490
                    if parent_node.ancestor_of is not None:
491
                        parent_node.ancestor_of = node.ancestor_of
492
                continue
493
            if node.parents is None:
494
                # This is a ghost
495
                continue
496
            # Now project the current nodes ancestor list to the parent nodes,
497
            # and queue them up to be walked
498
            if node.linear_dominator_node is not node:
499
                # We are at the tip of a long linear region
500
                # We know that there is nothing between here and the tail
501
                # that is interesting, so skip to the end
4371.3.21 by John Arbash Meinel
Attempt to make things better by splitting out left_parent and right_parent into
502
                if (self._process_parent(node, node.linear_dominator_node,
503
                                         candidate_nodes, queue)):
504
                    break
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
505
            else:
4371.3.31 by John Arbash Meinel
Change _KnownGraph.parents to be a tuple rather than a list.
506
               for pos from 0 <= pos < PyTuple_GET_SIZE(node.parents):
507
                   temp_node = PyTuple_GET_ITEM(node.parents, pos)
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
508
                   parent_node = <_KnownGraphNode>temp_node
4371.3.24 by John Arbash Meinel
A couple of cleanups.
509
                   if (self._process_parent(node, parent_node,
510
                                            candidate_nodes, queue)):
511
                       break
4371.3.28 by John Arbash Meinel
Finish converting away from 'cdef list' syntax.
512
        for pos from 0 <= pos < PyList_GET_SIZE(self._to_cleanup):
513
            temp_node = PyList_GET_ITEM(self._to_cleanup, pos)
514
            node = <_KnownGraphNode>temp_node
4371.3.19 by John Arbash Meinel
Get an initial pyrex implementation.
515
            node.ancestor_of = None
4371.3.22 by John Arbash Meinel
The slowdown was that _to_cleanup wasn't getting reset.
516
        self._to_cleanup = []
4371.3.20 by John Arbash Meinel
A few tweaks to the pyrex version.
517
        return PyFrozenSet_New(candidate_nodes)