/src/harfbuzz/src/hb-repacker.hh
Line | Count | Source |
1 | | /* |
2 | | * Copyright © 2020 Google, Inc. |
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 | | * Google Author(s): Garret Rieger |
25 | | */ |
26 | | |
27 | | #ifndef HB_REPACKER_HH |
28 | | #define HB_REPACKER_HH |
29 | | |
30 | | #include "hb-open-type.hh" |
31 | | #include "hb-map.hh" |
32 | | #include "hb-vector.hh" |
33 | | #include "graph/graph.hh" |
34 | | #include "graph/gsubgpos-graph.hh" |
35 | | #include "graph/serialize.hh" |
36 | | |
37 | | using graph::graph_t; |
38 | | |
39 | | /* |
40 | | * For a detailed writeup on the overflow resolution algorithm see: |
41 | | * docs/repacker.md |
42 | | */ |
43 | | |
44 | | struct lookup_size_t |
45 | | { |
46 | | unsigned lookup_index; |
47 | | size_t size; |
48 | | unsigned num_subtables; |
49 | | |
50 | | static int cmp (const void* a, const void* b) |
51 | 0 | { |
52 | 0 | return cmp ((const lookup_size_t*) a, |
53 | 0 | (const lookup_size_t*) b); |
54 | 0 | } |
55 | | |
56 | | static int cmp (const lookup_size_t* a, const lookup_size_t* b) |
57 | 4.45k | { |
58 | 4.45k | double subtables_per_byte_a = (double) a->num_subtables / (double) a->size; |
59 | 4.45k | double subtables_per_byte_b = (double) b->num_subtables / (double) b->size; |
60 | 4.45k | if (subtables_per_byte_a == subtables_per_byte_b) { |
61 | 282 | return b->lookup_index - a->lookup_index; |
62 | 282 | } |
63 | | |
64 | 4.17k | double cmp = subtables_per_byte_b - subtables_per_byte_a; |
65 | 4.17k | if (cmp < 0) return -1; |
66 | 2.73k | if (cmp > 0) return 1; |
67 | 0 | return 0; |
68 | 2.73k | } |
69 | | }; |
70 | | |
71 | | static inline |
72 | | bool _presplit_subtables_if_needed (graph::gsubgpos_graph_context_t& ext_context) |
73 | 1.86k | { |
74 | | // For each lookup this will check the size of subtables and split them as needed |
75 | | // so that no subtable is at risk of overflowing. (where we support splitting for |
76 | | // that subtable type). |
77 | | // |
78 | | // TODO(grieger): de-dup newly added nodes as necessary. Probably just want a full de-dup |
79 | | // pass after this processing is done. Not super necessary as splits are |
80 | | // only done where overflow is likely, so de-dup probably will get undone |
81 | | // later anyways. |
82 | | |
83 | | // The loop below can modify the contents of ext_context.lookups if new subtables are added |
84 | | // to a lookup during a split. So save the initial set of lookup indices so the iteration doesn't |
85 | | // risk access free'd memory if ext_context.lookups gets resized. |
86 | 1.86k | hb_set_t lookup_indices(ext_context.lookups.keys ()); |
87 | 1.86k | for (unsigned lookup_index : lookup_indices) |
88 | 4.73k | { |
89 | 4.73k | graph::Lookup* lookup = ext_context.lookups.get(lookup_index); |
90 | 4.73k | if (!lookup->split_subtables_if_needed (ext_context, lookup_index)) |
91 | 4 | return false; |
92 | 4.73k | } |
93 | | |
94 | 1.86k | return true; |
95 | 1.86k | } hb-subset.cc:_presplit_subtables_if_needed(graph::gsubgpos_graph_context_t&) Line | Count | Source | 73 | 1.86k | { | 74 | | // For each lookup this will check the size of subtables and split them as needed | 75 | | // so that no subtable is at risk of overflowing. (where we support splitting for | 76 | | // that subtable type). | 77 | | // | 78 | | // TODO(grieger): de-dup newly added nodes as necessary. Probably just want a full de-dup | 79 | | // pass after this processing is done. Not super necessary as splits are | 80 | | // only done where overflow is likely, so de-dup probably will get undone | 81 | | // later anyways. | 82 | | | 83 | | // The loop below can modify the contents of ext_context.lookups if new subtables are added | 84 | | // to a lookup during a split. So save the initial set of lookup indices so the iteration doesn't | 85 | | // risk access free'd memory if ext_context.lookups gets resized. | 86 | 1.86k | hb_set_t lookup_indices(ext_context.lookups.keys ()); | 87 | 1.86k | for (unsigned lookup_index : lookup_indices) | 88 | 4.73k | { | 89 | 4.73k | graph::Lookup* lookup = ext_context.lookups.get(lookup_index); | 90 | 4.73k | if (!lookup->split_subtables_if_needed (ext_context, lookup_index)) | 91 | 4 | return false; | 92 | 4.73k | } | 93 | | | 94 | 1.86k | return true; | 95 | 1.86k | } |
Unexecuted instantiation: hb-subset-table-layout.cc:_presplit_subtables_if_needed(graph::gsubgpos_graph_context_t&) Unexecuted instantiation: hb-subset-table-var.cc:_presplit_subtables_if_needed(graph::gsubgpos_graph_context_t&) Unexecuted instantiation: hb-subset-table-cff.cc:_presplit_subtables_if_needed(graph::gsubgpos_graph_context_t&) Unexecuted instantiation: hb-subset-table-color.cc:_presplit_subtables_if_needed(graph::gsubgpos_graph_context_t&) Unexecuted instantiation: hb-subset-table-other.cc:_presplit_subtables_if_needed(graph::gsubgpos_graph_context_t&) |
96 | | |
97 | | /* |
98 | | * Analyze the lookups in a GSUB/GPOS table and decide if any should be promoted |
99 | | * to extension lookups. |
100 | | */ |
101 | | static inline |
102 | | bool _promote_extensions_if_needed (graph::gsubgpos_graph_context_t& ext_context) |
103 | 1.86k | { |
104 | | // Simple Algorithm (v1, current): |
105 | | // 1. Calculate how many bytes each non-extension lookup consumes. |
106 | | // 2. Select up to 64k of those to remain as non-extension (greedy, highest subtables per byte first) |
107 | | // 3. Promote the rest. |
108 | | // |
109 | | // Advanced Algorithm (v2, not implemented): |
110 | | // 1. Perform connected component analysis using lookups as roots. |
111 | | // 2. Compute size of each connected component. |
112 | | // 3. Select up to 64k worth of connected components to remain as non-extensions. |
113 | | // (greedy, highest subtables per byte first) |
114 | | // 4. Promote the rest. |
115 | | |
116 | | // TODO(garretrieger): support extension demotion, then consider all lookups. Requires advanced algo. |
117 | | // TODO(garretrieger): also support extension promotion during iterative resolution phase, then |
118 | | // we can use a less conservative threshold here. |
119 | | // TODO(grieger): skip this for the 24 bit case. |
120 | 1.86k | if (!ext_context.lookups) return true; |
121 | | |
122 | 1.86k | unsigned total_lookup_table_sizes = 0; |
123 | 1.86k | hb_vector_t<lookup_size_t> lookup_sizes; |
124 | 1.86k | lookup_sizes.alloc (ext_context.lookups.get_population (), true); |
125 | | |
126 | 1.86k | for (unsigned lookup_index : ext_context.lookups.keys ()) |
127 | 4.72k | { |
128 | 4.72k | const auto& lookup_v = ext_context.graph.vertices_[lookup_index]; |
129 | 4.72k | total_lookup_table_sizes += lookup_v.table_size (); |
130 | | |
131 | 4.72k | const graph::Lookup* lookup = ext_context.lookups.get(lookup_index); |
132 | 4.72k | hb_set_t visited; |
133 | 4.72k | lookup_sizes.push (lookup_size_t { |
134 | 4.72k | lookup_index, |
135 | 4.72k | ext_context.graph.find_subgraph_size (lookup_index, visited), |
136 | 4.72k | lookup->number_of_subtables (), |
137 | 4.72k | }); |
138 | 4.72k | } |
139 | | |
140 | 1.86k | lookup_sizes.qsort (); |
141 | | |
142 | 1.86k | size_t lookup_list_size = ext_context.graph.vertices_[ext_context.lookup_list_index].table_size (); |
143 | 1.86k | size_t l2_l3_size = lookup_list_size + total_lookup_table_sizes; // Lookup List + Lookups |
144 | 1.86k | size_t l3_l4_size = total_lookup_table_sizes; // Lookups + SubTables |
145 | 1.86k | size_t l4_plus_size = 0; // SubTables + their descendants |
146 | | |
147 | | // Start by assuming all lookups are using extension subtables, this size will be removed later |
148 | | // if it's decided to not make a lookup extension. |
149 | 1.86k | for (auto p : lookup_sizes) |
150 | 4.72k | { |
151 | | // TODO(garretrieger): this overestimates the extension subtables size because some extension subtables may be |
152 | | // reused. However, we can't correct this until we have connected component analysis in place. |
153 | 4.72k | unsigned subtables_size = p.num_subtables * 8; |
154 | 4.72k | l3_l4_size += subtables_size; |
155 | 4.72k | l4_plus_size += subtables_size; |
156 | 4.72k | } |
157 | | |
158 | 1.86k | bool layers_full = false; |
159 | 1.86k | for (auto p : lookup_sizes) |
160 | 4.72k | { |
161 | 4.72k | const graph::Lookup* lookup = ext_context.lookups.get(p.lookup_index); |
162 | 4.72k | if (lookup->is_extension (ext_context.table_tag)) |
163 | | // already an extension so size is counted by the loop above. |
164 | 8 | continue; |
165 | | |
166 | 4.71k | if (!layers_full) |
167 | 3.91k | { |
168 | 3.91k | size_t lookup_size = ext_context.graph.vertices_[p.lookup_index].table_size (); |
169 | 3.91k | hb_set_t visited; |
170 | 3.91k | size_t subtables_size = ext_context.graph.find_subgraph_size (p.lookup_index, visited, 1) - lookup_size; |
171 | 3.91k | size_t remaining_size = p.size - subtables_size - lookup_size; |
172 | | |
173 | 3.91k | l3_l4_size += subtables_size; |
174 | 3.91k | l3_l4_size -= p.num_subtables * 8; |
175 | 3.91k | l4_plus_size += subtables_size + remaining_size; |
176 | | |
177 | 3.91k | if (l2_l3_size < (1 << 16) |
178 | 3.91k | && l3_l4_size < (1 << 16) |
179 | 3.90k | && l4_plus_size < (1 << 16)) continue; // this lookup fits within all layers groups |
180 | | |
181 | 1.83k | layers_full = true; |
182 | 1.83k | } |
183 | | |
184 | 2.63k | if (!ext_context.lookups.get(p.lookup_index)->make_extension (ext_context, p.lookup_index)) |
185 | 0 | return false; |
186 | 2.63k | } |
187 | | |
188 | 1.86k | return true; |
189 | 1.86k | } hb-subset.cc:_promote_extensions_if_needed(graph::gsubgpos_graph_context_t&) Line | Count | Source | 103 | 1.86k | { | 104 | | // Simple Algorithm (v1, current): | 105 | | // 1. Calculate how many bytes each non-extension lookup consumes. | 106 | | // 2. Select up to 64k of those to remain as non-extension (greedy, highest subtables per byte first) | 107 | | // 3. Promote the rest. | 108 | | // | 109 | | // Advanced Algorithm (v2, not implemented): | 110 | | // 1. Perform connected component analysis using lookups as roots. | 111 | | // 2. Compute size of each connected component. | 112 | | // 3. Select up to 64k worth of connected components to remain as non-extensions. | 113 | | // (greedy, highest subtables per byte first) | 114 | | // 4. Promote the rest. | 115 | | | 116 | | // TODO(garretrieger): support extension demotion, then consider all lookups. Requires advanced algo. | 117 | | // TODO(garretrieger): also support extension promotion during iterative resolution phase, then | 118 | | // we can use a less conservative threshold here. | 119 | | // TODO(grieger): skip this for the 24 bit case. | 120 | 1.86k | if (!ext_context.lookups) return true; | 121 | | | 122 | 1.86k | unsigned total_lookup_table_sizes = 0; | 123 | 1.86k | hb_vector_t<lookup_size_t> lookup_sizes; | 124 | 1.86k | lookup_sizes.alloc (ext_context.lookups.get_population (), true); | 125 | | | 126 | 1.86k | for (unsigned lookup_index : ext_context.lookups.keys ()) | 127 | 4.72k | { | 128 | 4.72k | const auto& lookup_v = ext_context.graph.vertices_[lookup_index]; | 129 | 4.72k | total_lookup_table_sizes += lookup_v.table_size (); | 130 | | | 131 | 4.72k | const graph::Lookup* lookup = ext_context.lookups.get(lookup_index); | 132 | 4.72k | hb_set_t visited; | 133 | 4.72k | lookup_sizes.push (lookup_size_t { | 134 | 4.72k | lookup_index, | 135 | 4.72k | ext_context.graph.find_subgraph_size (lookup_index, visited), | 136 | 4.72k | lookup->number_of_subtables (), | 137 | 4.72k | }); | 138 | 4.72k | } | 139 | | | 140 | 1.86k | lookup_sizes.qsort (); | 141 | | | 142 | 1.86k | size_t lookup_list_size = ext_context.graph.vertices_[ext_context.lookup_list_index].table_size (); | 143 | 1.86k | size_t l2_l3_size = lookup_list_size + total_lookup_table_sizes; // Lookup List + Lookups | 144 | 1.86k | size_t l3_l4_size = total_lookup_table_sizes; // Lookups + SubTables | 145 | 1.86k | size_t l4_plus_size = 0; // SubTables + their descendants | 146 | | | 147 | | // Start by assuming all lookups are using extension subtables, this size will be removed later | 148 | | // if it's decided to not make a lookup extension. | 149 | 1.86k | for (auto p : lookup_sizes) | 150 | 4.72k | { | 151 | | // TODO(garretrieger): this overestimates the extension subtables size because some extension subtables may be | 152 | | // reused. However, we can't correct this until we have connected component analysis in place. | 153 | 4.72k | unsigned subtables_size = p.num_subtables * 8; | 154 | 4.72k | l3_l4_size += subtables_size; | 155 | 4.72k | l4_plus_size += subtables_size; | 156 | 4.72k | } | 157 | | | 158 | 1.86k | bool layers_full = false; | 159 | 1.86k | for (auto p : lookup_sizes) | 160 | 4.72k | { | 161 | 4.72k | const graph::Lookup* lookup = ext_context.lookups.get(p.lookup_index); | 162 | 4.72k | if (lookup->is_extension (ext_context.table_tag)) | 163 | | // already an extension so size is counted by the loop above. | 164 | 8 | continue; | 165 | | | 166 | 4.71k | if (!layers_full) | 167 | 3.91k | { | 168 | 3.91k | size_t lookup_size = ext_context.graph.vertices_[p.lookup_index].table_size (); | 169 | 3.91k | hb_set_t visited; | 170 | 3.91k | size_t subtables_size = ext_context.graph.find_subgraph_size (p.lookup_index, visited, 1) - lookup_size; | 171 | 3.91k | size_t remaining_size = p.size - subtables_size - lookup_size; | 172 | | | 173 | 3.91k | l3_l4_size += subtables_size; | 174 | 3.91k | l3_l4_size -= p.num_subtables * 8; | 175 | 3.91k | l4_plus_size += subtables_size + remaining_size; | 176 | | | 177 | 3.91k | if (l2_l3_size < (1 << 16) | 178 | 3.91k | && l3_l4_size < (1 << 16) | 179 | 3.90k | && l4_plus_size < (1 << 16)) continue; // this lookup fits within all layers groups | 180 | | | 181 | 1.83k | layers_full = true; | 182 | 1.83k | } | 183 | | | 184 | 2.63k | if (!ext_context.lookups.get(p.lookup_index)->make_extension (ext_context, p.lookup_index)) | 185 | 0 | return false; | 186 | 2.63k | } | 187 | | | 188 | 1.86k | return true; | 189 | 1.86k | } |
Unexecuted instantiation: hb-subset-table-layout.cc:_promote_extensions_if_needed(graph::gsubgpos_graph_context_t&) Unexecuted instantiation: hb-subset-table-var.cc:_promote_extensions_if_needed(graph::gsubgpos_graph_context_t&) Unexecuted instantiation: hb-subset-table-cff.cc:_promote_extensions_if_needed(graph::gsubgpos_graph_context_t&) Unexecuted instantiation: hb-subset-table-color.cc:_promote_extensions_if_needed(graph::gsubgpos_graph_context_t&) Unexecuted instantiation: hb-subset-table-other.cc:_promote_extensions_if_needed(graph::gsubgpos_graph_context_t&) |
190 | | |
191 | | static inline |
192 | | bool _try_isolating_subgraphs (const hb_vector_t<graph::overflow_record_t>& overflows, |
193 | | graph_t& sorted_graph) |
194 | 32.3k | { |
195 | 32.3k | unsigned space = 0; |
196 | 32.3k | hb_set_t roots_to_isolate; |
197 | | |
198 | 8.22M | for (int i = overflows.length - 1; i >= 0; i--) |
199 | 8.19M | { |
200 | 8.19M | const graph::overflow_record_t& r = overflows[i]; |
201 | | |
202 | 8.19M | unsigned root; |
203 | 8.19M | unsigned overflow_space = sorted_graph.space_for (r.parent, &root); |
204 | 8.19M | if (!overflow_space) continue; |
205 | 2.54M | if (sorted_graph.num_roots_for_space (overflow_space) <= 1) continue; |
206 | | |
207 | 674k | if (!space) { |
208 | 7.38k | space = overflow_space; |
209 | 7.38k | } |
210 | | |
211 | 674k | if (space == overflow_space) |
212 | 338k | roots_to_isolate.add(root); |
213 | 674k | } |
214 | | |
215 | 32.3k | if (!roots_to_isolate) return false; |
216 | | |
217 | 7.38k | unsigned maximum_to_move = hb_max ((sorted_graph.num_roots_for_space (space) / 2u), 1u); |
218 | 7.38k | if (roots_to_isolate.get_population () > maximum_to_move) { |
219 | | // Only move at most half of the roots in a space at a time. |
220 | | // |
221 | | // Note: this was ported from non-stable ids to stable ids. So to retain the same behaviour |
222 | | // with regards to which roots are removed from the set we need to remove them in the topological |
223 | | // order, not the object id order. |
224 | 4.06k | int extra = roots_to_isolate.get_population () - maximum_to_move; |
225 | 2.42M | for (unsigned id : sorted_graph.ordering_) { |
226 | 2.42M | if (!extra) break; |
227 | 2.42M | if (roots_to_isolate.has(id)) { |
228 | 13.0k | roots_to_isolate.del(id); |
229 | 13.0k | extra--; |
230 | 13.0k | } |
231 | 2.42M | } |
232 | 4.06k | } |
233 | | |
234 | 7.38k | DEBUG_MSG (SUBSET_REPACK, nullptr, |
235 | 7.38k | "Overflow in space %u (%u roots). Moving %u roots to space %u.", |
236 | 7.38k | space, |
237 | 7.38k | sorted_graph.num_roots_for_space (space), |
238 | 7.38k | roots_to_isolate.get_population (), |
239 | 7.38k | sorted_graph.next_space ()); |
240 | | |
241 | 7.38k | sorted_graph.isolate_subgraph (roots_to_isolate); |
242 | 7.38k | sorted_graph.move_to_new_space (roots_to_isolate); |
243 | | |
244 | 7.38k | return true; |
245 | 32.3k | } hb-subset.cc:_try_isolating_subgraphs(hb_vector_t<graph::overflow_record_t, false> const&, graph::graph_t&) Line | Count | Source | 194 | 32.3k | { | 195 | 32.3k | unsigned space = 0; | 196 | 32.3k | hb_set_t roots_to_isolate; | 197 | | | 198 | 8.22M | for (int i = overflows.length - 1; i >= 0; i--) | 199 | 8.19M | { | 200 | 8.19M | const graph::overflow_record_t& r = overflows[i]; | 201 | | | 202 | 8.19M | unsigned root; | 203 | 8.19M | unsigned overflow_space = sorted_graph.space_for (r.parent, &root); | 204 | 8.19M | if (!overflow_space) continue; | 205 | 2.54M | if (sorted_graph.num_roots_for_space (overflow_space) <= 1) continue; | 206 | | | 207 | 674k | if (!space) { | 208 | 7.38k | space = overflow_space; | 209 | 7.38k | } | 210 | | | 211 | 674k | if (space == overflow_space) | 212 | 338k | roots_to_isolate.add(root); | 213 | 674k | } | 214 | | | 215 | 32.3k | if (!roots_to_isolate) return false; | 216 | | | 217 | 7.38k | unsigned maximum_to_move = hb_max ((sorted_graph.num_roots_for_space (space) / 2u), 1u); | 218 | 7.38k | if (roots_to_isolate.get_population () > maximum_to_move) { | 219 | | // Only move at most half of the roots in a space at a time. | 220 | | // | 221 | | // Note: this was ported from non-stable ids to stable ids. So to retain the same behaviour | 222 | | // with regards to which roots are removed from the set we need to remove them in the topological | 223 | | // order, not the object id order. | 224 | 4.06k | int extra = roots_to_isolate.get_population () - maximum_to_move; | 225 | 2.42M | for (unsigned id : sorted_graph.ordering_) { | 226 | 2.42M | if (!extra) break; | 227 | 2.42M | if (roots_to_isolate.has(id)) { | 228 | 13.0k | roots_to_isolate.del(id); | 229 | 13.0k | extra--; | 230 | 13.0k | } | 231 | 2.42M | } | 232 | 4.06k | } | 233 | | | 234 | 7.38k | DEBUG_MSG (SUBSET_REPACK, nullptr, | 235 | 7.38k | "Overflow in space %u (%u roots). Moving %u roots to space %u.", | 236 | 7.38k | space, | 237 | 7.38k | sorted_graph.num_roots_for_space (space), | 238 | 7.38k | roots_to_isolate.get_population (), | 239 | 7.38k | sorted_graph.next_space ()); | 240 | | | 241 | 7.38k | sorted_graph.isolate_subgraph (roots_to_isolate); | 242 | 7.38k | sorted_graph.move_to_new_space (roots_to_isolate); | 243 | | | 244 | 7.38k | return true; | 245 | 32.3k | } |
Unexecuted instantiation: hb-subset-table-layout.cc:_try_isolating_subgraphs(hb_vector_t<graph::overflow_record_t, false> const&, graph::graph_t&) Unexecuted instantiation: hb-subset-table-var.cc:_try_isolating_subgraphs(hb_vector_t<graph::overflow_record_t, false> const&, graph::graph_t&) Unexecuted instantiation: hb-subset-table-cff.cc:_try_isolating_subgraphs(hb_vector_t<graph::overflow_record_t, false> const&, graph::graph_t&) Unexecuted instantiation: hb-subset-table-color.cc:_try_isolating_subgraphs(hb_vector_t<graph::overflow_record_t, false> const&, graph::graph_t&) Unexecuted instantiation: hb-subset-table-other.cc:_try_isolating_subgraphs(hb_vector_t<graph::overflow_record_t, false> const&, graph::graph_t&) |
246 | | |
247 | | static inline |
248 | | bool _resolve_shared_overflow(const hb_vector_t<graph::overflow_record_t>& overflows, |
249 | | int overflow_index, |
250 | | graph_t& sorted_graph) |
251 | 150k | { |
252 | 150k | const graph::overflow_record_t& r = overflows[overflow_index]; |
253 | | |
254 | | // Find all of the parents in overflowing links that link to this |
255 | | // same child node. We will then try duplicating the child node and |
256 | | // re-assigning all of these parents to the duplicate. |
257 | 150k | hb_set_t parents; |
258 | 150k | parents.add(r.parent); |
259 | 89.6M | for (int i = overflow_index - 1; i >= 0; i--) { |
260 | 89.5M | const graph::overflow_record_t& r2 = overflows[i]; |
261 | 89.5M | if (r2.child == r.child) { |
262 | 150k | parents.add(r2.parent); |
263 | 150k | } |
264 | 89.5M | } |
265 | | |
266 | 150k | unsigned result = sorted_graph.duplicate(&parents, r.child); |
267 | 150k | if (result == (unsigned) -1 && parents.get_population() > 2) { |
268 | | // All links to the child are overflowing, so we can't include all |
269 | | // in the duplication. Remove one parent from the duplication. |
270 | | // Remove the lowest index parent, which will be the closest to the child. |
271 | 5.74k | parents.del(parents.get_min()); |
272 | 5.74k | result = sorted_graph.duplicate(&parents, r.child); |
273 | 5.74k | } |
274 | | |
275 | 150k | if (result == (unsigned) -1) return false; |
276 | | |
277 | 11.6k | if (parents.get_population() > 1) { |
278 | | // If the duplicated node has more than one parent pre-emptively raise it's priority to the maximum. |
279 | | // This will place it close to the parents. Node's with only one parent, don't need this as normal overflow |
280 | | // resolution will raise priority if needed. |
281 | | // |
282 | | // Reasoning: most of the parents to this child are likely at the same layer in the graph. Duplicating |
283 | | // the child will theoretically allow it to be placed closer to it's parents. However, due to the shortest |
284 | | // distance sort by default it's placement will remain in the same layer, thus it will remain in roughly the |
285 | | // same position (and distance from parents) as the original child node. The overflow resolution will attempt |
286 | | // to move nodes closer, but only for non-shared nodes. Since this node is shared, it will simply be given |
287 | | // further duplication which defeats the attempt to duplicate with multiple parents. To fix this we |
288 | | // pre-emptively raise priority now which allows the duplicated node to pack into the same layer as it's parents. |
289 | 6.63k | sorted_graph.vertices_[result].give_max_priority(); |
290 | 6.63k | } |
291 | | |
292 | 11.6k | return true; |
293 | 150k | } hb-subset.cc:_resolve_shared_overflow(hb_vector_t<graph::overflow_record_t, false> const&, int, graph::graph_t&) Line | Count | Source | 251 | 150k | { | 252 | 150k | const graph::overflow_record_t& r = overflows[overflow_index]; | 253 | | | 254 | | // Find all of the parents in overflowing links that link to this | 255 | | // same child node. We will then try duplicating the child node and | 256 | | // re-assigning all of these parents to the duplicate. | 257 | 150k | hb_set_t parents; | 258 | 150k | parents.add(r.parent); | 259 | 89.6M | for (int i = overflow_index - 1; i >= 0; i--) { | 260 | 89.5M | const graph::overflow_record_t& r2 = overflows[i]; | 261 | 89.5M | if (r2.child == r.child) { | 262 | 150k | parents.add(r2.parent); | 263 | 150k | } | 264 | 89.5M | } | 265 | | | 266 | 150k | unsigned result = sorted_graph.duplicate(&parents, r.child); | 267 | 150k | if (result == (unsigned) -1 && parents.get_population() > 2) { | 268 | | // All links to the child are overflowing, so we can't include all | 269 | | // in the duplication. Remove one parent from the duplication. | 270 | | // Remove the lowest index parent, which will be the closest to the child. | 271 | 5.74k | parents.del(parents.get_min()); | 272 | 5.74k | result = sorted_graph.duplicate(&parents, r.child); | 273 | 5.74k | } | 274 | | | 275 | 150k | if (result == (unsigned) -1) return false; | 276 | | | 277 | 11.6k | if (parents.get_population() > 1) { | 278 | | // If the duplicated node has more than one parent pre-emptively raise it's priority to the maximum. | 279 | | // This will place it close to the parents. Node's with only one parent, don't need this as normal overflow | 280 | | // resolution will raise priority if needed. | 281 | | // | 282 | | // Reasoning: most of the parents to this child are likely at the same layer in the graph. Duplicating | 283 | | // the child will theoretically allow it to be placed closer to it's parents. However, due to the shortest | 284 | | // distance sort by default it's placement will remain in the same layer, thus it will remain in roughly the | 285 | | // same position (and distance from parents) as the original child node. The overflow resolution will attempt | 286 | | // to move nodes closer, but only for non-shared nodes. Since this node is shared, it will simply be given | 287 | | // further duplication which defeats the attempt to duplicate with multiple parents. To fix this we | 288 | | // pre-emptively raise priority now which allows the duplicated node to pack into the same layer as it's parents. | 289 | 6.63k | sorted_graph.vertices_[result].give_max_priority(); | 290 | 6.63k | } | 291 | | | 292 | 11.6k | return true; | 293 | 150k | } |
Unexecuted instantiation: hb-subset-table-layout.cc:_resolve_shared_overflow(hb_vector_t<graph::overflow_record_t, false> const&, int, graph::graph_t&) Unexecuted instantiation: hb-subset-table-var.cc:_resolve_shared_overflow(hb_vector_t<graph::overflow_record_t, false> const&, int, graph::graph_t&) Unexecuted instantiation: hb-subset-table-cff.cc:_resolve_shared_overflow(hb_vector_t<graph::overflow_record_t, false> const&, int, graph::graph_t&) Unexecuted instantiation: hb-subset-table-color.cc:_resolve_shared_overflow(hb_vector_t<graph::overflow_record_t, false> const&, int, graph::graph_t&) Unexecuted instantiation: hb-subset-table-other.cc:_resolve_shared_overflow(hb_vector_t<graph::overflow_record_t, false> const&, int, graph::graph_t&) |
294 | | |
295 | | static inline |
296 | | bool _process_overflows (const hb_vector_t<graph::overflow_record_t>& overflows, |
297 | | hb_set_t& priority_bumped_parents, |
298 | | graph_t& sorted_graph) |
299 | 24.9k | { |
300 | 24.9k | bool resolution_attempted = false; |
301 | | |
302 | | // Try resolving the furthest overflows first. |
303 | 1.67M | for (int i = overflows.length - 1; i >= 0; i--) |
304 | 1.65M | { |
305 | 1.65M | const graph::overflow_record_t& r = overflows[i]; |
306 | 1.65M | const auto& child = sorted_graph.vertices_[r.child]; |
307 | 1.65M | if (child.is_shared ()) |
308 | 150k | { |
309 | | // The child object is shared, we may be able to eliminate the overflow |
310 | | // by duplicating it. |
311 | 150k | if (_resolve_shared_overflow(overflows, i, sorted_graph)) |
312 | 11.6k | return true; |
313 | | |
314 | | // Sometimes we can't duplicate a node which looks shared because it's not actually shared |
315 | | // (eg. all links from the same parent) in this case continue on to other resolution options. |
316 | 150k | } |
317 | | |
318 | 1.64M | if (child.is_leaf () && !priority_bumped_parents.has (r.parent)) |
319 | 857k | { |
320 | | // This object is too far from it's parent, attempt to move it closer. |
321 | | // |
322 | | // TODO(garretrieger): initially limiting this to leaf's since they can be |
323 | | // moved closer with fewer consequences. However, this can |
324 | | // likely can be used for non-leafs as well. |
325 | | // TODO(garretrieger): also try lowering priority of the parent. Make it |
326 | | // get placed further up in the ordering, closer to it's children. |
327 | | // this is probably preferable if the total size of the parent object |
328 | | // is < then the total size of the children (and the parent can be moved). |
329 | | // Since in that case moving the parent will cause a smaller increase in |
330 | | // the length of other offsets. |
331 | 857k | if (sorted_graph.raise_childrens_priority (r.parent)) { |
332 | 35.2k | priority_bumped_parents.add (r.parent); |
333 | 35.2k | resolution_attempted = true; |
334 | 35.2k | } |
335 | 857k | continue; |
336 | 857k | } |
337 | | |
338 | | // TODO(garretrieger): add additional offset resolution strategies |
339 | | // - Promotion to extension lookups. |
340 | | // - Table splitting. |
341 | 1.64M | } |
342 | | |
343 | 13.2k | return resolution_attempted; |
344 | 24.9k | } hb-subset.cc:_process_overflows(hb_vector_t<graph::overflow_record_t, false> const&, hb_set_t&, graph::graph_t&) Line | Count | Source | 299 | 24.9k | { | 300 | 24.9k | bool resolution_attempted = false; | 301 | | | 302 | | // Try resolving the furthest overflows first. | 303 | 1.67M | for (int i = overflows.length - 1; i >= 0; i--) | 304 | 1.65M | { | 305 | 1.65M | const graph::overflow_record_t& r = overflows[i]; | 306 | 1.65M | const auto& child = sorted_graph.vertices_[r.child]; | 307 | 1.65M | if (child.is_shared ()) | 308 | 150k | { | 309 | | // The child object is shared, we may be able to eliminate the overflow | 310 | | // by duplicating it. | 311 | 150k | if (_resolve_shared_overflow(overflows, i, sorted_graph)) | 312 | 11.6k | return true; | 313 | | | 314 | | // Sometimes we can't duplicate a node which looks shared because it's not actually shared | 315 | | // (eg. all links from the same parent) in this case continue on to other resolution options. | 316 | 150k | } | 317 | | | 318 | 1.64M | if (child.is_leaf () && !priority_bumped_parents.has (r.parent)) | 319 | 857k | { | 320 | | // This object is too far from it's parent, attempt to move it closer. | 321 | | // | 322 | | // TODO(garretrieger): initially limiting this to leaf's since they can be | 323 | | // moved closer with fewer consequences. However, this can | 324 | | // likely can be used for non-leafs as well. | 325 | | // TODO(garretrieger): also try lowering priority of the parent. Make it | 326 | | // get placed further up in the ordering, closer to it's children. | 327 | | // this is probably preferable if the total size of the parent object | 328 | | // is < then the total size of the children (and the parent can be moved). | 329 | | // Since in that case moving the parent will cause a smaller increase in | 330 | | // the length of other offsets. | 331 | 857k | if (sorted_graph.raise_childrens_priority (r.parent)) { | 332 | 35.2k | priority_bumped_parents.add (r.parent); | 333 | 35.2k | resolution_attempted = true; | 334 | 35.2k | } | 335 | 857k | continue; | 336 | 857k | } | 337 | | | 338 | | // TODO(garretrieger): add additional offset resolution strategies | 339 | | // - Promotion to extension lookups. | 340 | | // - Table splitting. | 341 | 1.64M | } | 342 | | | 343 | 13.2k | return resolution_attempted; | 344 | 24.9k | } |
Unexecuted instantiation: hb-subset-table-layout.cc:_process_overflows(hb_vector_t<graph::overflow_record_t, false> const&, hb_set_t&, graph::graph_t&) Unexecuted instantiation: hb-subset-table-var.cc:_process_overflows(hb_vector_t<graph::overflow_record_t, false> const&, hb_set_t&, graph::graph_t&) Unexecuted instantiation: hb-subset-table-cff.cc:_process_overflows(hb_vector_t<graph::overflow_record_t, false> const&, hb_set_t&, graph::graph_t&) Unexecuted instantiation: hb-subset-table-color.cc:_process_overflows(hb_vector_t<graph::overflow_record_t, false> const&, hb_set_t&, graph::graph_t&) Unexecuted instantiation: hb-subset-table-other.cc:_process_overflows(hb_vector_t<graph::overflow_record_t, false> const&, hb_set_t&, graph::graph_t&) |
345 | | |
346 | | inline bool |
347 | | hb_resolve_graph_overflows (hb_tag_t table_tag, |
348 | | unsigned max_rounds , |
349 | | bool always_recalculate_extensions, |
350 | | graph_t& sorted_graph /* IN/OUT */) |
351 | 4.19k | { |
352 | 4.19k | DEBUG_MSG (SUBSET_REPACK, nullptr, "Repacking %c%c%c%c.", HB_UNTAG(table_tag)); |
353 | 4.19k | sorted_graph.sort_shortest_distance (); |
354 | 4.19k | if (sorted_graph.in_error ()) |
355 | 0 | { |
356 | 0 | DEBUG_MSG (SUBSET_REPACK, nullptr, "Sorted graph in error state after initial sort."); |
357 | 0 | return false; |
358 | 0 | } |
359 | | |
360 | 4.19k | bool will_overflow = graph::will_overflow (sorted_graph); |
361 | 4.19k | if (!will_overflow) |
362 | 340 | return true; |
363 | | |
364 | 3.85k | bool is_gsub_or_gpos = (table_tag == HB_OT_TAG_GPOS || table_tag == HB_OT_TAG_GSUB); |
365 | 3.85k | graph::gsubgpos_graph_context_t ext_context (table_tag, sorted_graph); |
366 | 3.85k | if (is_gsub_or_gpos && will_overflow) |
367 | 3.76k | { |
368 | 3.76k | DEBUG_MSG (SUBSET_REPACK, nullptr, "Applying GSUB/GPOS repacking specializations."); |
369 | 3.76k | if (always_recalculate_extensions) |
370 | 1.86k | { |
371 | 1.86k | DEBUG_MSG (SUBSET_REPACK, nullptr, "Splitting subtables if needed."); |
372 | 1.86k | if (!_presplit_subtables_if_needed (ext_context)) { |
373 | 4 | DEBUG_MSG (SUBSET_REPACK, nullptr, "Subtable splitting failed."); |
374 | 4 | return false; |
375 | 4 | } |
376 | | |
377 | 1.86k | DEBUG_MSG (SUBSET_REPACK, nullptr, "Promoting lookups to extensions if needed."); |
378 | 1.86k | if (!_promote_extensions_if_needed (ext_context)) { |
379 | 0 | DEBUG_MSG (SUBSET_REPACK, nullptr, "Extensions promotion failed."); |
380 | 0 | return false; |
381 | 0 | } |
382 | 1.86k | } |
383 | | |
384 | 3.76k | DEBUG_MSG (SUBSET_REPACK, nullptr, "Assigning spaces to 32 bit subgraphs."); |
385 | 3.76k | if (sorted_graph.assign_spaces ()) |
386 | 2.32k | sorted_graph.sort_shortest_distance (); |
387 | 1.43k | else |
388 | 1.43k | sorted_graph.sort_shortest_distance_if_needed (); |
389 | 3.76k | } |
390 | | |
391 | 3.85k | unsigned round = 0; |
392 | 3.85k | unsigned total_iterations = 0; |
393 | 3.85k | hb_vector_t<graph::overflow_record_t> overflows; |
394 | | // TODO(garretrieger): select a good limit for max rounds. |
395 | 32.9k | while (!sorted_graph.in_error () |
396 | 32.6k | && graph::will_overflow (sorted_graph, &overflows) |
397 | 32.5k | && round < max_rounds |
398 | 32.3k | && total_iterations < HB_REPACKER_MAX_ITERATIONS) { |
399 | 32.3k | DEBUG_MSG (SUBSET_REPACK, nullptr, "=== Overflow resolution round %u ===", round); |
400 | 32.3k | print_overflows (sorted_graph, overflows); |
401 | | |
402 | 32.3k | total_iterations++; |
403 | 32.3k | hb_set_t priority_bumped_parents; |
404 | | |
405 | 32.3k | if (!_try_isolating_subgraphs (overflows, sorted_graph)) |
406 | 24.9k | { |
407 | 24.9k | round++; |
408 | 24.9k | if (!_process_overflows (overflows, priority_bumped_parents, sorted_graph)) |
409 | 3.26k | { |
410 | 3.26k | DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :("); |
411 | 3.26k | break; |
412 | 3.26k | } |
413 | 24.9k | } |
414 | | |
415 | 29.0k | sorted_graph.sort_shortest_distance (); |
416 | 29.0k | } |
417 | | |
418 | 3.85k | if (sorted_graph.in_error ()) |
419 | 292 | { |
420 | 292 | DEBUG_MSG (SUBSET_REPACK, nullptr, "Sorted graph in error state."); |
421 | 292 | return false; |
422 | 292 | } |
423 | | |
424 | 3.56k | if (graph::will_overflow (sorted_graph)) |
425 | 3.45k | { |
426 | 3.45k | if (is_gsub_or_gpos && !always_recalculate_extensions) { |
427 | | // If this a GSUB/GPOS table and we didn't try to extension promotion and table splitting then |
428 | | // as a last ditch effort, re-run the repacker with it enabled. |
429 | 1.86k | DEBUG_MSG (SUBSET_REPACK, nullptr, "Failed to find a resolution. Re-running with extension promotion and table splitting enabled."); |
430 | 1.86k | return hb_resolve_graph_overflows (table_tag, max_rounds, true, sorted_graph); |
431 | 1.86k | } |
432 | | |
433 | 1.58k | DEBUG_MSG (SUBSET_REPACK, nullptr, "Offset overflow resolution failed."); |
434 | 1.58k | return false; |
435 | 3.45k | } |
436 | | |
437 | 106 | return true; |
438 | 3.56k | } |
439 | | |
440 | | /* |
441 | | * Attempts to modify the topological sorting of the provided object graph to |
442 | | * eliminate offset overflows in the links between objects of the graph. If a |
443 | | * non-overflowing ordering is found the updated graph is serialized it into the |
444 | | * provided serialization context. |
445 | | * |
446 | | * If necessary the structure of the graph may be modified in ways that do not |
447 | | * affect the functionality of the graph. For example shared objects may be |
448 | | * duplicated. |
449 | | * |
450 | | * For a detailed writeup describing how the algorithm operates see: |
451 | | * docs/repacker.md |
452 | | */ |
453 | | template<typename T> |
454 | | inline hb_blob_t* |
455 | | hb_resolve_overflows (const T& packed, |
456 | | hb_tag_t table_tag, |
457 | | unsigned max_rounds = 32, |
458 | 2.33k | bool recalculate_extensions = false) { |
459 | 2.33k | graph_t sorted_graph (packed); |
460 | 2.33k | if (sorted_graph.in_error ()) |
461 | 0 | { |
462 | | // Invalid graph definition. |
463 | 0 | return nullptr; |
464 | 0 | } |
465 | | |
466 | 2.33k | if (!sorted_graph.is_fully_connected ()) |
467 | 0 | { |
468 | 0 | sorted_graph.print_orphaned_nodes (); |
469 | 0 | return nullptr; |
470 | 0 | } |
471 | | |
472 | 2.33k | if (sorted_graph.in_error ()) |
473 | 0 | { |
474 | | // Allocations failed somewhere |
475 | 0 | DEBUG_MSG (SUBSET_REPACK, nullptr, |
476 | 0 | "Graph is in error, likely due to a memory allocation error."); |
477 | 0 | return nullptr; |
478 | 0 | } |
479 | | |
480 | 2.33k | if (!hb_resolve_graph_overflows (table_tag, max_rounds, recalculate_extensions, sorted_graph)) |
481 | 1.88k | return nullptr; |
482 | | |
483 | 446 | return graph::serialize (sorted_graph); |
484 | 2.33k | } |
485 | | |
486 | | #endif /* HB_REPACKER_HH */ |