/src/harfbuzz/src/hb-decycler.hh
Line | Count | Source |
1 | | /* |
2 | | * Copyright © 2025 Behdad Esfahbod |
3 | | * |
4 | | * This is part of HarfBuzz, a text shaping library. |
5 | | * |
6 | | * Permission is hereby granted, without written agreement and without |
7 | | * license or royalty fees, to use, copy, modify, and distribute this |
8 | | * software and its documentation for any purpose, provided that the |
9 | | * above copyright notice and the following two paragraphs appear in |
10 | | * all copies of this software. |
11 | | * |
12 | | * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR |
13 | | * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES |
14 | | * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN |
15 | | * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
16 | | * DAMAGE. |
17 | | * |
18 | | * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, |
19 | | * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
20 | | * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS |
21 | | * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO |
22 | | * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
23 | | * |
24 | | * Author(s): Behdad Esfahbod |
25 | | */ |
26 | | |
27 | | #ifndef HB_DECYCLER_HH |
28 | | #define HB_DECYCLER_HH |
29 | | |
30 | | #include "hb.hh" |
31 | | |
32 | | /* |
33 | | * hb_decycler_t is an efficient cycle detector for graph traversal. |
34 | | * It's a simple tortoise-and-hare algorithm with a twist: it's |
35 | | * designed to detect cycles while traversing a graph in a DFS manner, |
36 | | * instead of just a linked list. |
37 | | * |
38 | | * For Floyd's tortoise and hare algorithm, see: |
39 | | * https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare |
40 | | * |
41 | | * hb_decycler_t is O(n) in the number of nodes in the DFS traversal |
42 | | * if there are no cycles. Unlike Floyd's algorithm, hb_decycler_t |
43 | | * can be used in a DFS traversal, where the graph is not a simple |
44 | | * linked list, but a tree with possible cycles. Like Floyd's algorithm, |
45 | | * it is constant-memory (~three pointers). |
46 | | * |
47 | | * The decycler works by creating an implicit linked-list on the stack, |
48 | | * of the path from the root to the current node, and apply Floyd's |
49 | | * algorithm on that list as it goes. |
50 | | * |
51 | | * The decycler is malloc-free, and as such, much faster to use than a |
52 | | * hb_set_t or hb_map_t equivalent. |
53 | | * |
54 | | * The decycler detects cycles in the graph *eventually*, not *immediately*. |
55 | | * That is, it may not detect a cycle until the cycle is fully traversed, |
56 | | * even multiple times. See Floyd's algorithm analysis for details. |
57 | | * |
58 | | * The implementation saves a pointer storage on the stack by combining |
59 | | * this->u.decycler and this->u.next into a union. This is possible because |
60 | | * at any point we only need one of those values. The invariant is that |
61 | | * after construction, and before destruction, of a node, the u.decycler |
62 | | * field is always valid. The u.next field is only valid when the node is |
63 | | * in the traversal path, parent to another node. |
64 | | * |
65 | | * There are three method's: |
66 | | * |
67 | | * - hb_decycler_node_t() constructor: Creates a new node in the traversal. |
68 | | * The constructor takes a reference to the decycler object and inserts |
69 | | * itself as the latest node in the traversal path, by advancing the hare |
70 | | * pointer, and for every other descent, advancing the tortoise pointer. |
71 | | * |
72 | | * - ~hb_decycler_node_t() destructor: Restores the decycler object to its |
73 | | * previous state by removing the node from the traversal path. |
74 | | * |
75 | | * - bool visit(uintptr_t value): Called on every node in the graph. Returns |
76 | | * true if the node is not part of a cycle, and false if it is. The value |
77 | | * parameter is used to detect cycles. It's the caller's responsibility |
78 | | * to ensure that the value is unique for each node in the graph. |
79 | | * The cycle detection is as simple as comparing the value to the value |
80 | | * held by the tortoise pointer, which is the Floyd's algorithm. |
81 | | * |
82 | | * For usage examples see test-decycler.cc. |
83 | | */ |
84 | | |
85 | | struct hb_decycler_node_t; |
86 | | |
87 | | struct hb_decycler_t |
88 | | { |
89 | | friend struct hb_decycler_node_t; |
90 | | |
91 | | private: |
92 | | bool tortoise_awake = false; |
93 | | hb_decycler_node_t *tortoise = nullptr; |
94 | | hb_decycler_node_t *hare = nullptr; |
95 | | }; |
96 | | |
97 | | struct hb_decycler_node_t |
98 | | { |
99 | | hb_decycler_node_t (hb_decycler_t &decycler) |
100 | 3.94M | { |
101 | 3.94M | u.decycler = &decycler; |
102 | | |
103 | 3.94M | decycler.tortoise_awake = !decycler.tortoise_awake; |
104 | | |
105 | 3.94M | if (!decycler.tortoise) |
106 | 193k | { |
107 | | // First node. |
108 | 193k | assert (decycler.tortoise_awake); |
109 | 193k | assert (!decycler.hare); |
110 | 193k | decycler.tortoise = decycler.hare = this; |
111 | 193k | return; |
112 | 193k | } |
113 | | |
114 | 3.74M | if (decycler.tortoise_awake) |
115 | 1.30M | decycler.tortoise = decycler.tortoise->u.next; // Time to move. |
116 | | |
117 | 3.74M | this->prev = decycler.hare; |
118 | 3.74M | decycler.hare->u.next = this; |
119 | 3.74M | decycler.hare = this; |
120 | 3.74M | } |
121 | | |
122 | | ~hb_decycler_node_t () |
123 | 3.94M | { |
124 | 3.94M | hb_decycler_t &decycler = *u.decycler; |
125 | | |
126 | | // Inverse of the constructor. |
127 | | |
128 | 3.94M | assert (decycler.hare == this); |
129 | 3.94M | decycler.hare = prev; |
130 | 3.94M | if (prev) |
131 | 3.74M | prev->u.decycler = &decycler; |
132 | | |
133 | 3.94M | assert (decycler.tortoise); |
134 | 3.94M | if (decycler.tortoise_awake) |
135 | 1.50M | decycler.tortoise = decycler.tortoise->prev; |
136 | | |
137 | 3.94M | decycler.tortoise_awake = !decycler.tortoise_awake; |
138 | 3.94M | } |
139 | | |
140 | | bool visit (uintptr_t value_) |
141 | 6.05M | { |
142 | 6.05M | value = value_; |
143 | | |
144 | 6.05M | hb_decycler_t &decycler = *u.decycler; |
145 | | |
146 | 6.05M | if (decycler.tortoise == this) |
147 | 874k | return true; // First node; not a cycle. |
148 | | |
149 | 5.18M | if (decycler.tortoise->value == value) |
150 | 1.06M | return false; // Cycle detected. |
151 | | |
152 | 4.11M | return true; |
153 | 5.18M | } |
154 | | |
155 | | private: |
156 | | union { |
157 | | hb_decycler_t *decycler; |
158 | | hb_decycler_node_t *next; |
159 | | } u = {nullptr}; |
160 | | hb_decycler_node_t *prev = nullptr; |
161 | | uintptr_t value = 0; |
162 | | }; |
163 | | |
164 | | #endif /* HB_DECYCLER_HH */ |