hb-repacker.hh (18120B)
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 { 52 return cmp ((const lookup_size_t*) a, 53 (const lookup_size_t*) b); 54 } 55 56 static int cmp (const lookup_size_t* a, const lookup_size_t* b) 57 { 58 double subtables_per_byte_a = (double) a->num_subtables / (double) a->size; 59 double subtables_per_byte_b = (double) b->num_subtables / (double) b->size; 60 if (subtables_per_byte_a == subtables_per_byte_b) { 61 return b->lookup_index - a->lookup_index; 62 } 63 64 double cmp = subtables_per_byte_b - subtables_per_byte_a; 65 if (cmp < 0) return -1; 66 if (cmp > 0) return 1; 67 return 0; 68 } 69 }; 70 71 static inline 72 bool _presplit_subtables_if_needed (graph::gsubgpos_graph_context_t& ext_context) 73 { 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 hb_set_t lookup_indices(ext_context.lookups.keys ()); 87 for (unsigned lookup_index : lookup_indices) 88 { 89 graph::Lookup* lookup = ext_context.lookups.get(lookup_index); 90 if (!lookup->split_subtables_if_needed (ext_context, lookup_index)) 91 return false; 92 } 93 94 return true; 95 } 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 { 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 if (!ext_context.lookups) return true; 121 122 unsigned total_lookup_table_sizes = 0; 123 hb_vector_t<lookup_size_t> lookup_sizes; 124 lookup_sizes.alloc (ext_context.lookups.get_population (), true); 125 126 for (unsigned lookup_index : ext_context.lookups.keys ()) 127 { 128 const auto& lookup_v = ext_context.graph.vertices_[lookup_index]; 129 total_lookup_table_sizes += lookup_v.table_size (); 130 131 const graph::Lookup* lookup = ext_context.lookups.get(lookup_index); 132 hb_set_t visited; 133 lookup_sizes.push (lookup_size_t { 134 lookup_index, 135 ext_context.graph.find_subgraph_size (lookup_index, visited), 136 lookup->number_of_subtables (), 137 }); 138 } 139 140 lookup_sizes.qsort (); 141 142 size_t lookup_list_size = ext_context.graph.vertices_[ext_context.lookup_list_index].table_size (); 143 size_t l2_l3_size = lookup_list_size + total_lookup_table_sizes; // Lookup List + Lookups 144 size_t l3_l4_size = total_lookup_table_sizes; // Lookups + SubTables 145 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 for (auto p : lookup_sizes) 150 { 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 unsigned subtables_size = p.num_subtables * 8; 154 l3_l4_size += subtables_size; 155 l4_plus_size += subtables_size; 156 } 157 158 bool layers_full = false; 159 for (auto p : lookup_sizes) 160 { 161 const graph::Lookup* lookup = ext_context.lookups.get(p.lookup_index); 162 if (lookup->is_extension (ext_context.table_tag)) 163 // already an extension so size is counted by the loop above. 164 continue; 165 166 if (!layers_full) 167 { 168 size_t lookup_size = ext_context.graph.vertices_[p.lookup_index].table_size (); 169 hb_set_t visited; 170 size_t subtables_size = ext_context.graph.find_subgraph_size (p.lookup_index, visited, 1) - lookup_size; 171 size_t remaining_size = p.size - subtables_size - lookup_size; 172 173 l3_l4_size += subtables_size; 174 l3_l4_size -= p.num_subtables * 8; 175 l4_plus_size += subtables_size + remaining_size; 176 177 if (l2_l3_size < (1 << 16) 178 && l3_l4_size < (1 << 16) 179 && l4_plus_size < (1 << 16)) continue; // this lookup fits within all layers groups 180 181 layers_full = true; 182 } 183 184 if (!ext_context.lookups.get(p.lookup_index)->make_extension (ext_context, p.lookup_index)) 185 return false; 186 } 187 188 return true; 189 } 190 191 static inline 192 bool _try_isolating_subgraphs (const hb_vector_t<graph::overflow_record_t>& overflows, 193 graph_t& sorted_graph) 194 { 195 unsigned space = 0; 196 hb_set_t roots_to_isolate; 197 198 for (int i = overflows.length - 1; i >= 0; i--) 199 { 200 const graph::overflow_record_t& r = overflows[i]; 201 202 unsigned root; 203 unsigned overflow_space = sorted_graph.space_for (r.parent, &root); 204 if (!overflow_space) continue; 205 if (sorted_graph.num_roots_for_space (overflow_space) <= 1) continue; 206 207 if (!space) { 208 space = overflow_space; 209 } 210 211 if (space == overflow_space) 212 roots_to_isolate.add(root); 213 } 214 215 if (!roots_to_isolate) return false; 216 217 unsigned maximum_to_move = hb_max ((sorted_graph.num_roots_for_space (space) / 2u), 1u); 218 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 int extra = roots_to_isolate.get_population () - maximum_to_move; 225 for (unsigned id : sorted_graph.ordering_) { 226 if (!extra) break; 227 if (roots_to_isolate.has(id)) { 228 roots_to_isolate.del(id); 229 extra--; 230 } 231 } 232 } 233 234 DEBUG_MSG (SUBSET_REPACK, nullptr, 235 "Overflow in space %u (%u roots). Moving %u roots to space %u.", 236 space, 237 sorted_graph.num_roots_for_space (space), 238 roots_to_isolate.get_population (), 239 sorted_graph.next_space ()); 240 241 sorted_graph.isolate_subgraph (roots_to_isolate); 242 sorted_graph.move_to_new_space (roots_to_isolate); 243 244 return true; 245 } 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 { 252 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 hb_set_t parents; 258 parents.add(r.parent); 259 for (int i = overflow_index - 1; i >= 0; i--) { 260 const graph::overflow_record_t& r2 = overflows[i]; 261 if (r2.child == r.child) { 262 parents.add(r2.parent); 263 } 264 } 265 266 unsigned result = sorted_graph.duplicate(&parents, r.child); 267 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 parents.del(parents.get_min()); 272 result = sorted_graph.duplicate(&parents, r.child); 273 } 274 275 if (result == (unsigned) -1) return false; 276 277 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 sorted_graph.vertices_[result].give_max_priority(); 290 } 291 292 return true; 293 } 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 { 300 bool resolution_attempted = false; 301 302 // Try resolving the furthest overflows first. 303 for (int i = overflows.length - 1; i >= 0; i--) 304 { 305 const graph::overflow_record_t& r = overflows[i]; 306 const auto& child = sorted_graph.vertices_[r.child]; 307 if (child.is_shared ()) 308 { 309 // The child object is shared, we may be able to eliminate the overflow 310 // by duplicating it. 311 if (_resolve_shared_overflow(overflows, i, sorted_graph)) 312 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 } 317 318 if (child.is_leaf () && !priority_bumped_parents.has (r.parent)) 319 { 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 if (sorted_graph.raise_childrens_priority (r.parent)) { 332 priority_bumped_parents.add (r.parent); 333 resolution_attempted = true; 334 } 335 continue; 336 } 337 338 // TODO(garretrieger): add additional offset resolution strategies 339 // - Promotion to extension lookups. 340 // - Table splitting. 341 } 342 343 return resolution_attempted; 344 } 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 { 352 DEBUG_MSG (SUBSET_REPACK, nullptr, "Repacking %c%c%c%c.", HB_UNTAG(table_tag)); 353 sorted_graph.sort_shortest_distance (); 354 if (sorted_graph.in_error ()) 355 { 356 DEBUG_MSG (SUBSET_REPACK, nullptr, "Sorted graph in error state after initial sort."); 357 return false; 358 } 359 360 bool will_overflow = graph::will_overflow (sorted_graph); 361 if (!will_overflow) 362 return true; 363 364 bool is_gsub_or_gpos = (table_tag == HB_OT_TAG_GPOS || table_tag == HB_OT_TAG_GSUB); 365 graph::gsubgpos_graph_context_t ext_context (table_tag, sorted_graph); 366 if (is_gsub_or_gpos && will_overflow) 367 { 368 DEBUG_MSG (SUBSET_REPACK, nullptr, "Applying GSUB/GPOS repacking specializations."); 369 if (always_recalculate_extensions) 370 { 371 DEBUG_MSG (SUBSET_REPACK, nullptr, "Splitting subtables if needed."); 372 if (!_presplit_subtables_if_needed (ext_context)) { 373 DEBUG_MSG (SUBSET_REPACK, nullptr, "Subtable splitting failed."); 374 return false; 375 } 376 377 DEBUG_MSG (SUBSET_REPACK, nullptr, "Promoting lookups to extensions if needed."); 378 if (!_promote_extensions_if_needed (ext_context)) { 379 DEBUG_MSG (SUBSET_REPACK, nullptr, "Extensions promotion failed."); 380 return false; 381 } 382 } 383 384 DEBUG_MSG (SUBSET_REPACK, nullptr, "Assigning spaces to 32 bit subgraphs."); 385 if (sorted_graph.assign_spaces ()) 386 sorted_graph.sort_shortest_distance (); 387 else 388 sorted_graph.sort_shortest_distance_if_needed (); 389 } 390 391 unsigned round = 0; 392 hb_vector_t<graph::overflow_record_t> overflows; 393 // TODO(garretrieger): select a good limit for max rounds. 394 while (!sorted_graph.in_error () 395 && graph::will_overflow (sorted_graph, &overflows) 396 && round < max_rounds) { 397 DEBUG_MSG (SUBSET_REPACK, nullptr, "=== Overflow resolution round %u ===", round); 398 print_overflows (sorted_graph, overflows); 399 400 hb_set_t priority_bumped_parents; 401 402 if (!_try_isolating_subgraphs (overflows, sorted_graph)) 403 { 404 // Don't count space isolation towards round limit. Only increment 405 // round counter if space isolation made no changes. 406 round++; 407 if (!_process_overflows (overflows, priority_bumped_parents, sorted_graph)) 408 { 409 DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :("); 410 break; 411 } 412 } 413 414 sorted_graph.sort_shortest_distance (); 415 } 416 417 if (sorted_graph.in_error ()) 418 { 419 DEBUG_MSG (SUBSET_REPACK, nullptr, "Sorted graph in error state."); 420 return false; 421 } 422 423 if (graph::will_overflow (sorted_graph)) 424 { 425 if (is_gsub_or_gpos && !always_recalculate_extensions) { 426 // If this a GSUB/GPOS table and we didn't try to extension promotion and table splitting then 427 // as a last ditch effort, re-run the repacker with it enabled. 428 DEBUG_MSG (SUBSET_REPACK, nullptr, "Failed to find a resolution. Re-running with extension promotion and table splitting enabled."); 429 return hb_resolve_graph_overflows (table_tag, max_rounds, true, sorted_graph); 430 } 431 432 DEBUG_MSG (SUBSET_REPACK, nullptr, "Offset overflow resolution failed."); 433 return false; 434 } 435 436 return true; 437 } 438 439 /* 440 * Attempts to modify the topological sorting of the provided object graph to 441 * eliminate offset overflows in the links between objects of the graph. If a 442 * non-overflowing ordering is found the updated graph is serialized it into the 443 * provided serialization context. 444 * 445 * If necessary the structure of the graph may be modified in ways that do not 446 * affect the functionality of the graph. For example shared objects may be 447 * duplicated. 448 * 449 * For a detailed writeup describing how the algorithm operates see: 450 * docs/repacker.md 451 */ 452 template<typename T> 453 inline hb_blob_t* 454 hb_resolve_overflows (const T& packed, 455 hb_tag_t table_tag, 456 unsigned max_rounds = 32, 457 bool recalculate_extensions = false) { 458 graph_t sorted_graph (packed); 459 if (sorted_graph.in_error ()) 460 { 461 // Invalid graph definition. 462 return nullptr; 463 } 464 465 if (!sorted_graph.is_fully_connected ()) 466 { 467 sorted_graph.print_orphaned_nodes (); 468 return nullptr; 469 } 470 471 if (sorted_graph.in_error ()) 472 { 473 // Allocations failed somewhere 474 DEBUG_MSG (SUBSET_REPACK, nullptr, 475 "Graph is in error, likely due to a memory allocation error."); 476 return nullptr; 477 } 478 479 if (!hb_resolve_graph_overflows (table_tag, max_rounds, recalculate_extensions, sorted_graph)) 480 return nullptr; 481 482 return graph::serialize (sorted_graph); 483 } 484 485 #endif /* HB_REPACKER_HH */