From 1584d3cb8faf244ae439cd59eac5f3d006d7a106 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Wed, 28 Oct 2020 17:49:09 -0700 Subject: [PATCH 01/30] [subset] Start a proof of concept implementation of the GSUB/GPOS offset overflow resolver. --- src/Makefile.am | 6 +- src/Makefile.sources | 1 + src/hb-repacker.hh | 180 +++++++++++++++++++++++++++++++++++++++++++ src/hb-serialize.hh | 3 + src/test-repacker.cc | 87 +++++++++++++++++++++ 5 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 src/hb-repacker.hh create mode 100644 src/test-repacker.cc diff --git a/src/Makefile.am b/src/Makefile.am index e10068e94..20e9be32f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -342,7 +342,7 @@ test_gsub_would_substitute_SOURCES = test-gsub-would-substitute.cc test_gsub_would_substitute_CPPFLAGS = $(HBCFLAGS) $(FREETYPE_CFLAGS) test_gsub_would_substitute_LDADD = libharfbuzz.la $(HBLIBS) $(FREETYPE_LIBS) -COMPILED_TESTS = test-algs test-array test-iter test-meta test-number test-ot-tag test-unicode-ranges test-bimap +COMPILED_TESTS = test-algs test-array test-iter test-meta test-number test-ot-tag test-unicode-ranges test-bimap test-repacker COMPILED_TESTS_CPPFLAGS = $(HBCFLAGS) -DMAIN -UNDEBUG COMPILED_TESTS_LDADD = libharfbuzz.la $(HBLIBS) check_PROGRAMS += $(COMPILED_TESTS) @@ -356,6 +356,10 @@ test_array_SOURCES = test-array.cc test_array_CPPFLAGS = $(HBCFLAGS) test_array_LDADD = libharfbuzz.la $(HBLIBS) +test_repacker_SOURCES = test-repacker.cc hb-static.cc +test_repacker_CPPFLAGS = $(HBCFLAGS) +test_repacker_LDADD = libharfbuzz.la libharfbuzz-subset.la $(HBLIBS) + test_iter_SOURCES = test-iter.cc hb-static.cc test_iter_CPPFLAGS = $(COMPILED_TESTS_CPPFLAGS) test_iter_LDADD = $(COMPILED_TESTS_LDADD) diff --git a/src/Makefile.sources b/src/Makefile.sources index 6a6d3018b..933ae8850 100644 --- a/src/Makefile.sources +++ b/src/Makefile.sources @@ -268,6 +268,7 @@ HB_SUBSET_sources = \ hb-subset-plan.hh \ hb-subset.cc \ hb-subset.hh \ + hb-repacker.hh \ $(NULL) HB_SUBSET_headers = \ diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh new file mode 100644 index 000000000..4bcc2393a --- /dev/null +++ b/src/hb-repacker.hh @@ -0,0 +1,180 @@ +/* + * Copyright © 2020 Google, Inc. + * + * This is part of HarfBuzz, a text shaping library. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, provided that the + * above copyright notice and the following two paragraphs appear in + * all copies of this software. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES + * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN + * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * Google Author(s): Garret Rieger + */ + +#ifndef HB_REPACKER_HH +#define HB_REPACKER_HH + +#include "hb-open-type.hh" +#include "hb-serialize.hh" +#include "hb-vector.hh" + + +struct graph_t +{ + /* + * A topological sorting of an object graph. Ordered + * in reverse serialization order (first object in the + * serialization is at the end of the graph). This matches + * the 'packed' object stack used internally in the + * serializer + */ + graph_t (const hb_vector_t& objects) + : objects_ (objects) + {} + + /* + * serialize graph into the provided serialization buffer. + */ + void serialize (hb_serialize_context_t* c) + { + c->start_serialize (); + for (unsigned i = 0; i < objects_.length; i++) { + if (!objects_[i]) continue; + + c->push (); + + size_t size = objects_[i]->tail - objects_[i]->head; + char* start = c->allocate_size (size); + if (!start) return; + + memcpy (start, objects_[i]->head, size); + + for (const auto& link : objects_[i]->links) + serialize_link (link, start, c); + + c->pop_pack (false); + } + c->end_serialize (); + } + + /* + * Generates a new topological sorting of graph using BFS. + */ + void sort_bfs () + { + hb_vector_t queue; + hb_vector_t sorted_graph; + + // Object graphs are in reverse order, the first object is at the end + // of the vector. + queue.push (objects_.length - 1); + + hb_set_t visited; + while (queue.length) + { + int next_id = queue[0]; + queue.remove(0); + visited.add(next_id); + + hb_serialize_context_t::object_t* next = objects_[next_id]; + sorted_graph.push (next); + + for (const auto& link : next->links) { + if (!visited.has (link.objidx)) + queue.push (link.objidx); + } + } + + sorted_graph.as_array ().reverse (); + objects_ = sorted_graph; + // TODO(garretrieger): remap object id's on the links. + // TODO(garretrieger): what order should graphs be in (first object at the end? or the beginning) + + // TODO(garretrieger): check that all objects made it over into the sorted copy + // (ie. all objects are connected in the original graph). + } + + /* + * Will any offsets overflow on graph when it's serialized? + */ + bool will_overflow() + { + // TODO(garretrieger): implement me. + return false; + } + + private: + + template void + serialize_link_of_type (const hb_serialize_context_t::object_t::link_t& link, + char* head, + hb_serialize_context_t* c) + { + OT::Offset* offset = reinterpret_cast*> (head + link.position); + *offset = 0; + c->add_link (*offset, + link.objidx, + (hb_serialize_context_t::whence_t) link.whence, + link.bias); + } + + void serialize_link (const hb_serialize_context_t::object_t::link_t& link, + char* head, + hb_serialize_context_t* c) + { + if (link.is_wide) + { + if (link.is_signed) + { + serialize_link_of_type (link, head, c); + } else { + serialize_link_of_type (link, head, c); + } + } else { + if (link.is_signed) + { + serialize_link_of_type (link, head, c); + } else { + serialize_link_of_type (link, head, c); + } + } + } + + hb_vector_t objects_; +}; + + +/* + * Re-serialize the provided object graph into the serialization context + * using BFS (Breadth First Search) to produce the topological ordering. + */ +inline void +hb_resolve_overflows (const hb_vector_t& packed, + hb_serialize_context_t* c) { + graph_t sorted_graph (packed); + sorted_graph.sort_bfs (); + if (sorted_graph.will_overflow ()) { + // TODO(garretrieger): additional offset resolution strategies + // - Promotion to extension lookups. + // - Table duplication. + // - Table splitting. + } + + sorted_graph.serialize (c); +} + + +#endif /* HB_REPACKER_HH */ diff --git a/src/hb-serialize.hh b/src/hb-serialize.hh index fe29bdf96..d863a2c3a 100644 --- a/src/hb-serialize.hh +++ b/src/hb-serialize.hh @@ -520,6 +520,9 @@ struct hb_serialize_context_t (char *) b.arrayZ, free); } + const hb_vector_t& object_graph() + { return packed; } + private: template void assign_offset (const object_t* parent, const object_t::link_t &link, unsigned offset) diff --git a/src/test-repacker.cc b/src/test-repacker.cc new file mode 100644 index 000000000..c94804054 --- /dev/null +++ b/src/test-repacker.cc @@ -0,0 +1,87 @@ +/* + * Copyright © 2020 Google, Inc. + * + * This is part of HarfBuzz, a text shaping library. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, provided that the + * above copyright notice and the following two paragraphs appear in + * all copies of this software. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES + * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN + * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * Google Author(s): Garret Rieger + */ + +#include "hb-repacker.hh" +#include "hb-open-type.hh" + +static void +populate_serializer (hb_serialize_context_t* c) +{ + c->start_serialize (); + c->push (); + char* obj = c->allocate_size (3); + strncpy (obj, "ghi", 3); + unsigned obj_3 = c->pop_pack (); + + c->push (); + obj = c->allocate_size (3); + strncpy (obj, "def", 3); + unsigned obj_2 = c->pop_pack (); + + c->push (); + obj = c->allocate_size (3); + strncpy (obj, "abc", 3); + + OT::Offset16* offset = c->start_embed (); + c->extend_min (offset); + c->add_link (*offset, obj_2); + + offset = c->start_embed (); + c->extend_min (offset); + c->add_link (*offset, obj_3); + + c->pop_pack (); + + c->end_serialize(); +} + +static void +test_serialize () +{ + size_t buffer_size = 100; + void* buffer_1 = malloc (buffer_size); + hb_serialize_context_t c1 (buffer_1, buffer_size); + populate_serializer (&c1); + hb_bytes_t expected = c1.copy_bytes (); + + void* buffer_2 = malloc (buffer_size); + hb_serialize_context_t c2 (buffer_2, buffer_size); + + graph_t graph (c1.object_graph ()); + graph.serialize (&c2); + hb_bytes_t actual = c2.copy_bytes (); + + assert (actual == expected); + + free (buffer_1); + free (buffer_2); +} + +int +main (int argc, char **argv) +{ + test_serialize (); +} From 00f393dc3fdd40a761df4fe988745ecb0e62df4b Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 29 Oct 2020 14:58:34 -0700 Subject: [PATCH 02/30] [subset] finish up BFS sort implementation. --- src/hb-repacker.hh | 93 ++++++++++++++++++++++++++++++++++--------- src/test-repacker.cc | 95 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 151 insertions(+), 37 deletions(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index 4bcc2393a..2be1433f6 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -28,6 +28,7 @@ #define HB_REPACKER_HH #include "hb-open-type.hh" +#include "hb-map.hh" #include "hb-serialize.hh" #include "hb-vector.hh" @@ -42,8 +43,30 @@ struct graph_t * serializer */ graph_t (const hb_vector_t& objects) - : objects_ (objects) - {} + { + bool removed_nil = false; + for (unsigned i = 0; i < objects.length; i++) + { + // If this graph came from a serialization buffer object 0 is the + // nil object. We don't need it for our purposes here so drop it. + if (i == 0 && !objects[i]) + { + removed_nil = true; + continue; + } + + auto* copy = objects_.push (*objects[i]); + if (!removed_nil) continue; + for (unsigned i = 0; i < copy->links.length; i++) + // Fix indices to account for removed nil object. + copy->links[i].objidx--; + } + } + + ~graph_t () + { + objects_.fini_deep (); + } /* * serialize graph into the provided serialization buffer. @@ -52,17 +75,15 @@ struct graph_t { c->start_serialize (); for (unsigned i = 0; i < objects_.length; i++) { - if (!objects_[i]) continue; - c->push (); - size_t size = objects_[i]->tail - objects_[i]->head; + size_t size = objects_[i].tail - objects_[i].head; char* start = c->allocate_size (size); if (!start) return; - memcpy (start, objects_[i]->head, size); + memcpy (start, objects_[i].head, size); - for (const auto& link : objects_[i]->links) + for (const auto& link : objects_[i].links) serialize_link (link, start, c); c->pop_pack (false); @@ -75,36 +96,65 @@ struct graph_t */ void sort_bfs () { - hb_vector_t queue; - hb_vector_t sorted_graph; + // BFS doesn't always produce a topological sort so this is just + // for testing re-ordering capabilities for now. + // Will need to use a more advanced topological sorting algorithm + + if (objects_.length <= 1) { + // Graph of 1 or less doesn't need sorting. + return; + } + + hb_vector_t queue; + hb_vector_t sorted_graph; + hb_map_t id_map; // Object graphs are in reverse order, the first object is at the end // of the vector. queue.push (objects_.length - 1); + int new_id = objects_.length - 1; hb_set_t visited; while (queue.length) { - int next_id = queue[0]; + unsigned next_id = queue[0]; queue.remove(0); visited.add(next_id); - hb_serialize_context_t::object_t* next = objects_[next_id]; + hb_serialize_context_t::object_t& next = objects_[next_id]; sorted_graph.push (next); + id_map.set (next_id, new_id--); - for (const auto& link : next->links) { + for (const auto& link : next.links) { if (!visited.has (link.objidx)) queue.push (link.objidx); } } + if (new_id != -1) + { + // Graph is not fully connected, there are unsorted objects. + // TODO(garretrieger): handle this. + assert (false); + } + + // Apply objidx remapping. + // TODO(garretrieger): extract this to a helper. + for (unsigned i = 0; i < sorted_graph.length; i++) + { + for (unsigned j = 0; j < sorted_graph[i].links.length; j++) + { + auto& link = sorted_graph[i].links[j]; + if (!id_map.has (link.objidx)) + // TODO(garretrieger): handle this. + assert (false); + link.objidx = id_map.get (link.objidx); + } + } + sorted_graph.as_array ().reverse (); objects_ = sorted_graph; - // TODO(garretrieger): remap object id's on the links. - // TODO(garretrieger): what order should graphs be in (first object at the end? or the beginning) - - // TODO(garretrieger): check that all objects made it over into the sorted copy - // (ie. all objects are connected in the original graph). + sorted_graph.fini_deep (); } /* @@ -113,6 +163,8 @@ struct graph_t bool will_overflow() { // TODO(garretrieger): implement me. + // Check for offsets that exceed their width or are negative if + // using a non-signed link. return false; } @@ -126,7 +178,9 @@ struct graph_t OT::Offset* offset = reinterpret_cast*> (head + link.position); *offset = 0; c->add_link (*offset, - link.objidx, + // serializer has an extra nil object at the start of the + // object array. So all id's are +1 of what our id's are. + link.objidx + 1, (hb_serialize_context_t::whence_t) link.whence, link.bias); } @@ -153,7 +207,8 @@ struct graph_t } } - hb_vector_t objects_; + public: + hb_vector_t objects_; }; diff --git a/src/test-repacker.cc b/src/test-repacker.cc index c94804054..c181eb02e 100644 --- a/src/test-repacker.cc +++ b/src/test-repacker.cc @@ -27,44 +27,102 @@ #include "hb-repacker.hh" #include "hb-open-type.hh" -static void -populate_serializer (hb_serialize_context_t* c) +static void start_object(const char* tag, + unsigned len, + hb_serialize_context_t* c) { - c->start_serialize (); c->push (); - char* obj = c->allocate_size (3); - strncpy (obj, "ghi", 3); - unsigned obj_3 = c->pop_pack (); + char* obj = c->allocate_size (len); + strncpy (obj, tag, len); +} - c->push (); - obj = c->allocate_size (3); - strncpy (obj, "def", 3); - unsigned obj_2 = c->pop_pack (); - c->push (); - obj = c->allocate_size (3); - strncpy (obj, "abc", 3); +static unsigned add_object(const char* tag, + unsigned len, + hb_serialize_context_t* c) +{ + start_object (tag, len, c); + return c->pop_pack (false); +} + +static void add_offset (unsigned id, + hb_serialize_context_t* c) +{ OT::Offset16* offset = c->start_embed (); c->extend_min (offset); - c->add_link (*offset, obj_2); + c->add_link (*offset, id); +} - offset = c->start_embed (); - c->extend_min (offset); - c->add_link (*offset, obj_3); +static void +populate_serializer_simple (hb_serialize_context_t* c) +{ + c->start_serialize (); + unsigned obj_1 = add_object ("ghi", 3, c); + unsigned obj_2 = add_object ("def", 3, c); + + start_object ("abc", 3, c); + add_offset (obj_2, c); + add_offset (obj_1, c); c->pop_pack (); c->end_serialize(); } +static void +populate_serializer_complex (hb_serialize_context_t* c) +{ + c->start_serialize (); + + unsigned obj_4 = add_object ("jkl", 3, c); + unsigned obj_3 = add_object ("ghi", 3, c); + + start_object ("def", 3, c); + add_offset (obj_3, c); + unsigned obj_2 = c->pop_pack (false); + + start_object ("abc", 3, c); + add_offset (obj_2, c); + add_offset (obj_4, c); + c->pop_pack (); + + c->end_serialize(); +} + +static void test_sort_bfs () +{ + size_t buffer_size = 100; + void* buffer = malloc (buffer_size); + hb_serialize_context_t c (buffer, buffer_size); + populate_serializer_complex (&c); + + graph_t graph (c.object_graph ()); + graph.sort_bfs (); + + assert(strncmp (graph.objects_[3].head, "abc", 3) == 0); + assert(graph.objects_[3].links.length == 2); + assert(graph.objects_[3].links[0].objidx == 2); + assert(graph.objects_[3].links[1].objidx == 1); + + assert(strncmp (graph.objects_[2].head, "def", 3) == 0); + assert(graph.objects_[2].links.length == 1); + assert(graph.objects_[2].links[0].objidx == 0); + + assert(strncmp (graph.objects_[1].head, "jkl", 3) == 0); + assert(graph.objects_[1].links.length == 0); + + assert(strncmp (graph.objects_[0].head, "ghi", 3) == 0); + assert(graph.objects_[0].links.length == 0); +} + static void test_serialize () { size_t buffer_size = 100; void* buffer_1 = malloc (buffer_size); hb_serialize_context_t c1 (buffer_1, buffer_size); - populate_serializer (&c1); + populate_serializer_simple (&c1); hb_bytes_t expected = c1.copy_bytes (); void* buffer_2 = malloc (buffer_size); @@ -84,4 +142,5 @@ int main (int argc, char **argv) { test_serialize (); + test_sort_bfs (); } From f4c78cc7dd11c83aa3f3a3516e75f4fe689aff19 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Fri, 30 Oct 2020 10:29:51 -0700 Subject: [PATCH 03/30] [subset] Implement Kahn's algo for topological sorting instead of BFS. --- src/hb-repacker.hh | 88 ++++++++++++++++++++++++++++++-------------- src/test-repacker.cc | 69 +++++++++++++++++++++++++++++++--- 2 files changed, 124 insertions(+), 33 deletions(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index 2be1433f6..9d8fa2db7 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -35,10 +35,13 @@ struct graph_t { + // TODO(garretrieger): add an error tracking system similar to what serialize_context_t + // does. + /* * A topological sorting of an object graph. Ordered * in reverse serialization order (first object in the - * serialization is at the end of the graph). This matches + * serialization is at the end of the list). This matches * the 'packed' object stack used internally in the * serializer */ @@ -92,14 +95,11 @@ struct graph_t } /* - * Generates a new topological sorting of graph using BFS. + * Generates a new topological sorting of graph using Kahn's + * algorithm: https://en.wikipedia.org/wiki/Topological_sorting#Algorithms */ - void sort_bfs () + void sort_kahn () { - // BFS doesn't always produce a topological sort so this is just - // for testing re-ordering capabilities for now. - // Will need to use a more advanced topological sorting algorithm - if (objects_.length <= 1) { // Graph of 1 or less doesn't need sorting. return; @@ -108,25 +108,28 @@ struct graph_t hb_vector_t queue; hb_vector_t sorted_graph; hb_map_t id_map; + hb_map_t edge_count; + incoming_edge_count (&edge_count); // Object graphs are in reverse order, the first object is at the end - // of the vector. + // of the vector. Since the graph is topologically sorted it's safe to + // assume the first object has no incoming edges. queue.push (objects_.length - 1); int new_id = objects_.length - 1; - hb_set_t visited; while (queue.length) { unsigned next_id = queue[0]; queue.remove(0); - visited.add(next_id); hb_serialize_context_t::object_t& next = objects_[next_id]; sorted_graph.push (next); id_map.set (next_id, new_id--); for (const auto& link : next.links) { - if (!visited.has (link.objidx)) + // TODO(garretrieger): sort children from smallest to largest + edge_count.set (link.objidx, edge_count.get (link.objidx) - 1); + if (!edge_count.get (link.objidx)) queue.push (link.objidx); } } @@ -138,19 +141,7 @@ struct graph_t assert (false); } - // Apply objidx remapping. - // TODO(garretrieger): extract this to a helper. - for (unsigned i = 0; i < sorted_graph.length; i++) - { - for (unsigned j = 0; j < sorted_graph[i].links.length; j++) - { - auto& link = sorted_graph[i].links[j]; - if (!id_map.has (link.objidx)) - // TODO(garretrieger): handle this. - assert (false); - link.objidx = id_map.get (link.objidx); - } - } + remap_obj_indices (id_map, &sorted_graph); sorted_graph.as_array ().reverse (); objects_ = sorted_graph; @@ -163,13 +154,53 @@ struct graph_t bool will_overflow() { // TODO(garretrieger): implement me. - // Check for offsets that exceed their width or are negative if - // using a non-signed link. + // - Check for offsets that exceed their width or; + // - are negative if using a non-signed link. return false; } private: + /* + * Updates all objidx's in all links using the provided mapping. + */ + void remap_obj_indices (const hb_map_t& id_map, + hb_vector_t* sorted_graph) + { + for (unsigned i = 0; i < sorted_graph->length; i++) + { + for (unsigned j = 0; j < (*sorted_graph)[i].links.length; j++) + { + auto& link = (*sorted_graph)[i].links[j]; + if (!id_map.has (link.objidx)) + // TODO(garretrieger): handle this. + assert (false); + link.objidx = id_map.get (link.objidx); + } + } + } + + /* + * Creates a map from objid to # of incoming edges. + */ + void incoming_edge_count (hb_map_t* out) + { + for (unsigned i = 0; i < objects_.length; i++) + { + if (!out->has (i)) + out->set (i, 0); + + for (const auto& l : objects_[i].links) + { + unsigned id = l.objidx; + if (out->has (id)) + out->set (id, out->get (id) + 1); + else + out->set (id, 1); + } + } + } + template void serialize_link_of_type (const hb_serialize_context_t::object_t::link_t& link, char* head, @@ -220,9 +251,10 @@ inline void hb_resolve_overflows (const hb_vector_t& packed, hb_serialize_context_t* c) { graph_t sorted_graph (packed); - sorted_graph.sort_bfs (); + sorted_graph.sort_kahn (); if (sorted_graph.will_overflow ()) { - // TODO(garretrieger): additional offset resolution strategies + // TODO(garretrieger): try additional offset resolution strategies + // - Dijkstra sort of weighted graph. // - Promotion to extension lookups. // - Table duplication. // - Table splitting. diff --git a/src/test-repacker.cc b/src/test-repacker.cc index c181eb02e..4b287b62a 100644 --- a/src/test-repacker.cc +++ b/src/test-repacker.cc @@ -71,7 +71,7 @@ populate_serializer_simple (hb_serialize_context_t* c) } static void -populate_serializer_complex (hb_serialize_context_t* c) +populate_serializer_complex_1 (hb_serialize_context_t* c) { c->start_serialize (); @@ -90,15 +90,41 @@ populate_serializer_complex (hb_serialize_context_t* c) c->end_serialize(); } -static void test_sort_bfs () +static void +populate_serializer_complex_2 (hb_serialize_context_t* c) +{ + c->start_serialize (); + + unsigned obj_5 = add_object ("mno", 3, c); + + unsigned obj_4 = add_object ("jkl", 3, c); + + start_object ("ghi", 3, c); + add_offset (obj_4, c); + unsigned obj_3 = c->pop_pack (false); + + start_object ("def", 3, c); + add_offset (obj_3, c); + unsigned obj_2 = c->pop_pack (false); + + start_object ("abc", 3, c); + add_offset (obj_2, c); + add_offset (obj_4, c); + add_offset (obj_5, c); + c->pop_pack (); + + c->end_serialize(); +} + +static void test_sort_kahn_1 () { size_t buffer_size = 100; void* buffer = malloc (buffer_size); hb_serialize_context_t c (buffer, buffer_size); - populate_serializer_complex (&c); + populate_serializer_complex_1 (&c); graph_t graph (c.object_graph ()); - graph.sort_bfs (); + graph.sort_kahn (); assert(strncmp (graph.objects_[3].head, "abc", 3) == 0); assert(graph.objects_[3].links.length == 2); @@ -116,6 +142,38 @@ static void test_sort_bfs () assert(graph.objects_[0].links.length == 0); } +static void test_sort_kahn_2 () +{ + size_t buffer_size = 100; + void* buffer = malloc (buffer_size); + hb_serialize_context_t c (buffer, buffer_size); + populate_serializer_complex_2 (&c); + + graph_t graph (c.object_graph ()); + graph.sort_kahn (); + + + assert(strncmp (graph.objects_[4].head, "abc", 3) == 0); + assert(graph.objects_[4].links.length == 3); + assert(graph.objects_[4].links[0].objidx == 3); + assert(graph.objects_[4].links[1].objidx == 0); + assert(graph.objects_[4].links[2].objidx == 2); + + assert(strncmp (graph.objects_[3].head, "def", 3) == 0); + assert(graph.objects_[3].links.length == 1); + assert(graph.objects_[3].links[0].objidx == 1); + + assert(strncmp (graph.objects_[2].head, "mno", 3) == 0); + assert(graph.objects_[2].links.length == 0); + + assert(strncmp (graph.objects_[1].head, "ghi", 3) == 0); + assert(graph.objects_[1].links.length == 1); + assert(graph.objects_[1].links[0].objidx == 0); + + assert(strncmp (graph.objects_[0].head, "jkl", 3) == 0); + assert(graph.objects_[0].links.length == 0); +} + static void test_serialize () { @@ -142,5 +200,6 @@ int main (int argc, char **argv) { test_serialize (); - test_sort_bfs (); + test_sort_kahn_1 (); + test_sort_kahn_2 (); } From 6b1ea4cbe724af10309763b708abc36c968f14a7 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Fri, 30 Oct 2020 12:16:26 -0700 Subject: [PATCH 04/30] [subset] hook up the repacker to run if offset overflows are encountered during subsetting. --- src/hb-serialize.hh | 13 +++++++++++-- src/hb-subset.cc | 30 +++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/hb-serialize.hh b/src/hb-serialize.hh index d863a2c3a..7afe61710 100644 --- a/src/hb-serialize.hh +++ b/src/hb-serialize.hh @@ -136,7 +136,14 @@ struct hb_serialize_context_t template bool check_equal (T1 &&v1, T2 &&v2) - { return check_success ((long long) v1 == (long long) v2); } + { + if ((long long) v1 != (long long) v2) + { + err_offset_overflow (); + return false; + } + return true; + } template bool check_assign (T1 &v1, T2 &&v2) @@ -400,6 +407,7 @@ struct hb_serialize_context_t /* Following two functions exist to allow setting breakpoint on. */ void err_ran_out_of_room () { this->ran_out_of_room = true; } + void err_offset_overflow () { this->offset_overflow = true; } void err_other_error () { this->successful = false; } template @@ -520,7 +528,7 @@ struct hb_serialize_context_t (char *) b.arrayZ, free); } - const hb_vector_t& object_graph() + const hb_vector_t& object_graph() const { return packed; } private: @@ -537,6 +545,7 @@ struct hb_serialize_context_t unsigned int debug_depth; bool successful; bool ran_out_of_room; + bool offset_overflow; private: diff --git a/src/hb-subset.cc b/src/hb-subset.cc index 8b77ecd45..c68f64bc6 100644 --- a/src/hb-subset.cc +++ b/src/hb-subset.cc @@ -50,6 +50,7 @@ #include "hb-ot-layout-gpos-table.hh" #include "hb-ot-var-gvar-table.hh" #include "hb-ot-var-hvar-table.hh" +#include "hb-repacker.hh" static unsigned @@ -64,6 +65,32 @@ _plan_estimate_subset_table_size (hb_subset_plan_t *plan, unsigned table_len) return 512 + (unsigned) (table_len * sqrt ((double) dst_glyphs / src_glyphs)); } +/* + * Repack the serialization buffer if any offset overflows exist. + */ +static hb_blob_t* +_repack (const hb_serialize_context_t& c) +{ + if (!c.offset_overflow) + return c.copy_blob (); + + hb_vector_t buf; + int buf_size = c.end - c.start; + if (unlikely (!buf.alloc (buf_size))) + return nullptr; + + hb_serialize_context_t repacked ((void *) buf, buf_size); + hb_resolve_overflows (c.object_graph (), &repacked); + + if (unlikely (repacked.ran_out_of_room || repacked.in_error () || repacked.offset_overflow)) + // TODO(garretrieger): refactor so we can share the resize/retry logic with the subset + // portion. + return nullptr; + + return repacked.copy_blob (); +} + + template static bool _subset (hb_subset_plan_t *plan) @@ -111,7 +138,8 @@ _subset (hb_subset_plan_t *plan) { if (needed) { - hb_blob_t *dest_blob = serializer.copy_blob (); + hb_blob_t *dest_blob = _repack (serializer); + if (!dest_blob) return false; DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c final subset table size: %u bytes.", HB_UNTAG (tag), dest_blob->length); result = c.plan->add_table (tag, dest_blob); hb_blob_destroy (dest_blob); From 8ebe5d734f3543b7a1266f252fe96188efc69531 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Mon, 2 Nov 2020 14:51:39 -0800 Subject: [PATCH 05/30] Implement will_overflow (). --- src/hb-repacker.hh | 73 +++++++++++++++++++++++++++++++++++++++++--- src/test-repacker.cc | 45 +++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 4 deletions(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index 9d8fa2db7..e103a8ef4 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -50,6 +50,8 @@ struct graph_t bool removed_nil = false; for (unsigned i = 0; i < objects.length; i++) { + // TODO(grieger): check all links point to valid objects. + // If this graph came from a serialization buffer object 0 is the // nil object. We don't need it for our purposes here so drop it. if (i == 0 && !objects[i]) @@ -151,16 +153,79 @@ struct graph_t /* * Will any offsets overflow on graph when it's serialized? */ - bool will_overflow() + bool will_overflow () { - // TODO(garretrieger): implement me. - // - Check for offsets that exceed their width or; - // - are negative if using a non-signed link. + hb_map_t start_positions; + hb_map_t end_positions; + + unsigned current_pos = 0; + for (int i = objects_.length - 1; i >= 0; i--) + { + start_positions.set (i, current_pos); + current_pos += objects_[i].tail - objects_[i].head; + end_positions.set (i, current_pos); + } + + + for (unsigned parent_idx = 0; parent_idx < objects_.length; parent_idx++) + { + for (const auto& link : objects_[parent_idx].links) + { + int64_t offset = compute_offset (parent_idx, + link, + start_positions, + end_positions); + + if (!is_valid_offset (offset, link)) return true; + } + } + return false; } private: + int64_t compute_offset ( + unsigned parent_idx, + const hb_serialize_context_t::object_t::link_t& link, + const hb_map_t& start_positions, + const hb_map_t& end_positions) + { + unsigned child_idx = link.objidx; + int64_t offset = 0; + switch ((hb_serialize_context_t::whence_t) link.whence) { + case hb_serialize_context_t::whence_t::Head: + offset = start_positions[child_idx] - start_positions[parent_idx]; break; + case hb_serialize_context_t::whence_t::Tail: + offset = start_positions[child_idx] - end_positions[parent_idx]; break; + case hb_serialize_context_t::whence_t::Absolute: + offset = start_positions[child_idx]; break; + } + + assert (offset >= link.bias); + offset -= link.bias; + return offset; + } + + bool is_valid_offset (int64_t offset, + const hb_serialize_context_t::object_t::link_t& link) + { + if (link.is_signed) + { + if (link.is_wide) + return offset >= -((int64_t) 1 << 31) && offset < ((int64_t) 1 << 31); + else + return offset >= -(1 << 15) && offset < (1 << 15); + } + else + { + if (link.is_wide) + return offset >= 0 && offset < ((int64_t) 1 << 32); + else + return offset >= 0 && offset < (1 << 16); + } + } + /* * Updates all objidx's in all links using the provided mapping. */ diff --git a/src/test-repacker.cc b/src/test-repacker.cc index 4b287b62a..280db6bdc 100644 --- a/src/test-repacker.cc +++ b/src/test-repacker.cc @@ -24,6 +24,8 @@ * Google Author(s): Garret Rieger */ +#include + #include "hb-repacker.hh" #include "hb-open-type.hh" @@ -70,6 +72,25 @@ populate_serializer_simple (hb_serialize_context_t* c) c->end_serialize(); } +static void +populate_serializer_with_overflow (hb_serialize_context_t* c) +{ + std::string large_string(40000, 'a'); + c->start_serialize (); + + unsigned obj_1 = add_object (large_string.c_str(), 40000, c); + unsigned obj_2 = add_object (large_string.c_str(), 40000, c); + unsigned obj_3 = add_object (large_string.c_str(), 40000, c); + + start_object ("abc", 3, c); + add_offset (obj_3, c); + add_offset (obj_2, c); + add_offset (obj_1, c); + c->pop_pack (); + + c->end_serialize(); +} + static void populate_serializer_complex_1 (hb_serialize_context_t* c) { @@ -196,10 +217,34 @@ test_serialize () free (buffer_2); } +static void test_will_overflow_1 () +{ + size_t buffer_size = 100; + void* buffer = malloc (buffer_size); + hb_serialize_context_t c (buffer, buffer_size); + populate_serializer_complex_2 (&c); + graph_t graph (c.object_graph ()); + + assert (!graph.will_overflow ()); +} + +static void test_will_overflow_2 () +{ + size_t buffer_size = 160000; + void* buffer = malloc (buffer_size); + hb_serialize_context_t c (buffer, buffer_size); + populate_serializer_with_overflow (&c); + graph_t graph (c.object_graph ()); + + assert (graph.will_overflow ()); +} + int main (int argc, char **argv) { test_serialize (); test_sort_kahn_1 (); test_sort_kahn_2 (); + test_will_overflow_1 (); + test_will_overflow_2 (); } From aaa7873d425a6267b1df16f5a1f3750578b438f0 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Mon, 2 Nov 2020 16:16:27 -0800 Subject: [PATCH 06/30] [subset] add topological sort by closest distance via Dijkstra's algorithm. --- src/hb-repacker.hh | 125 +++++++++++++++++++++++++++++++++++++++++++ src/test-repacker.cc | 37 ++++++++++++- 2 files changed, 160 insertions(+), 2 deletions(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index e103a8ef4..90d73b30c 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -150,6 +150,62 @@ struct graph_t sorted_graph.fini_deep (); } + /* + * Generates a new topological sorting of graph ordered by the shortest + * distance to each node. + */ + void sort_shortest_distance () + { + if (objects_.length <= 1) { + // Graph of 1 or less doesn't need sorting. + return; + } + + hb_hashmap_t distance_to; + compute_distances (&distance_to); + + hb_set_t queue; + hb_vector_t sorted_graph; + hb_map_t id_map; + hb_map_t edge_count; + incoming_edge_count (&edge_count); + + // Object graphs are in reverse order, the first object is at the end + // of the vector. Since the graph is topologically sorted it's safe to + // assume the first object has no incoming edges. + queue.add (objects_.length - 1); + int new_id = objects_.length - 1; + + while (queue.get_population ()) + { + unsigned next_id = closest_object (queue, distance_to); + queue.del (next_id); + + hb_serialize_context_t::object_t& next = objects_[next_id]; + sorted_graph.push (next); + id_map.set (next_id, new_id--); + + for (const auto& link : next.links) { + edge_count.set (link.objidx, edge_count.get (link.objidx) - 1); + if (!edge_count.get (link.objidx)) + queue.add (link.objidx); + } + } + + if (new_id != -1) + { + // Graph is not fully connected, there are unsorted objects. + // TODO(garretrieger): handle this. + assert (false); + } + + remap_obj_indices (id_map, &sorted_graph); + + sorted_graph.as_array ().reverse (); + objects_ = sorted_graph; + sorted_graph.fini_deep (); + } + /* * Will any offsets overflow on graph when it's serialized? */ @@ -185,6 +241,74 @@ struct graph_t private: + unsigned closest_object (const hb_set_t& queue, + const hb_hashmap_t& distance_to) + { + int64_t closest_distance = hb_int_max (int64_t); + unsigned closest_index = -1; + for (unsigned i : queue) + { + if (distance_to.get (i) < closest_distance) + { + closest_distance = distance_to.get (i); + closest_index = i; + } + } + assert (closest_index != (unsigned) -1); + return closest_index; + } + + /* + * Finds the distance too each object in the graph + * from the initial node. + */ + void compute_distances (hb_hashmap_t* distance_to) + { + // Uses Dijkstra's algorithm to find all of the shortest distances. + // https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm + distance_to->clear (); + hb_set_t unvisited; + unvisited.add_range (0, objects_.length - 1); + + unsigned current_idx = objects_.length - 1; + distance_to->set (current_idx, 0); + + while (unvisited.get_population ()) + { + const auto& current = objects_[current_idx]; + int current_distance = (*distance_to)[current_idx]; + + for (const auto& link : current.links) + { + if (!unvisited.has (link.objidx)) continue; + + const auto& child = objects_[link.objidx]; + int64_t child_weight = child.tail - child.head + + (!link.is_wide ? (1 << 16) : ((int64_t) 1 << 32)); + int64_t child_distance = current_distance + child_weight; + + if (child_distance < distance_to->get (link.objidx)) + distance_to->set (link.objidx, child_distance); + } + + unvisited.del (current_idx); + + // TODO(garretrieger): change this to use a priority queue. + int64_t smallest_distance = hb_int_max(int64_t); + for (hb_codepoint_t idx : unvisited) + { + if (distance_to->get (idx) < smallest_distance) + { + smallest_distance = distance_to->get (idx); + current_idx = idx; + } + } + + // TODO(garretrieger): this will trigger if graph is disconnected. Handle this. + assert (!unvisited.get_population () || smallest_distance != hb_int_max (int64_t)); + } + } + int64_t compute_offset ( unsigned parent_idx, const hb_serialize_context_t::object_t::link_t& link, @@ -318,6 +442,7 @@ hb_resolve_overflows (const hb_vector_t& pac graph_t sorted_graph (packed); sorted_graph.sort_kahn (); if (sorted_graph.will_overflow ()) { + sorted_graph.sort_shortest_distance (); // TODO(garretrieger): try additional offset resolution strategies // - Dijkstra sort of weighted graph. // - Promotion to extension lookups. diff --git a/src/test-repacker.cc b/src/test-repacker.cc index 280db6bdc..46554e1cd 100644 --- a/src/test-repacker.cc +++ b/src/test-repacker.cc @@ -116,7 +116,7 @@ populate_serializer_complex_2 (hb_serialize_context_t* c) { c->start_serialize (); - unsigned obj_5 = add_object ("mno", 3, c); + unsigned obj_5 = add_object ("mn", 3, c); unsigned obj_4 = add_object ("jkl", 3, c); @@ -184,7 +184,7 @@ static void test_sort_kahn_2 () assert(graph.objects_[3].links.length == 1); assert(graph.objects_[3].links[0].objidx == 1); - assert(strncmp (graph.objects_[2].head, "mno", 3) == 0); + assert(strncmp (graph.objects_[2].head, "mn", 2) == 0); assert(graph.objects_[2].links.length == 0); assert(strncmp (graph.objects_[1].head, "ghi", 3) == 0); @@ -195,6 +195,38 @@ static void test_sort_kahn_2 () assert(graph.objects_[0].links.length == 0); } +static void test_sort_shortest () +{ + size_t buffer_size = 100; + void* buffer = malloc (buffer_size); + hb_serialize_context_t c (buffer, buffer_size); + populate_serializer_complex_2 (&c); + + graph_t graph (c.object_graph ()); + graph.sort_shortest_distance (); + + + assert(strncmp (graph.objects_[4].head, "abc", 3) == 0); + assert(graph.objects_[4].links.length == 3); + assert(graph.objects_[4].links[0].objidx == 2); + assert(graph.objects_[4].links[1].objidx == 0); + assert(graph.objects_[4].links[2].objidx == 3); + + assert(strncmp (graph.objects_[3].head, "mn", 2) == 0); + assert(graph.objects_[3].links.length == 0); + + assert(strncmp (graph.objects_[2].head, "def", 3) == 0); + assert(graph.objects_[2].links.length == 1); + assert(graph.objects_[2].links[0].objidx == 1); + + assert(strncmp (graph.objects_[1].head, "ghi", 3) == 0); + assert(graph.objects_[1].links.length == 1); + assert(graph.objects_[1].links[0].objidx == 0); + + assert(strncmp (graph.objects_[0].head, "jkl", 3) == 0); + assert(graph.objects_[0].links.length == 0); +} + static void test_serialize () { @@ -245,6 +277,7 @@ main (int argc, char **argv) test_serialize (); test_sort_kahn_1 (); test_sort_kahn_2 (); + test_sort_shortest (); test_will_overflow_1 (); test_will_overflow_2 (); } From dd8e5d0e1b0c52190bf16ab091ee3756b30d4d97 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Tue, 3 Nov 2020 14:01:42 -0800 Subject: [PATCH 07/30] [subset] Only run the repacker for GSUB/GPOS. --- src/hb-subset.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/hb-subset.cc b/src/hb-subset.cc index c68f64bc6..2db027034 100644 --- a/src/hb-subset.cc +++ b/src/hb-subset.cc @@ -69,8 +69,12 @@ _plan_estimate_subset_table_size (hb_subset_plan_t *plan, unsigned table_len) * Repack the serialization buffer if any offset overflows exist. */ static hb_blob_t* -_repack (const hb_serialize_context_t& c) +_repack (hb_tag_t tag, const hb_serialize_context_t& c) { + if (tag != HB_OT_TAG_GPOS + && tag != HB_OT_TAG_GSUB) + return c.copy_blob (); + if (!c.offset_overflow) return c.copy_blob (); @@ -90,7 +94,6 @@ _repack (const hb_serialize_context_t& c) return repacked.copy_blob (); } - template static bool _subset (hb_subset_plan_t *plan) @@ -138,7 +141,7 @@ _subset (hb_subset_plan_t *plan) { if (needed) { - hb_blob_t *dest_blob = _repack (serializer); + hb_blob_t *dest_blob = _repack (tag, serializer); if (!dest_blob) return false; DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c final subset table size: %u bytes.", HB_UNTAG (tag), dest_blob->length); result = c.plan->add_table (tag, dest_blob); From 5c4e0ffd9768de0c51a42baa35d9c29636fdd99a Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Wed, 4 Nov 2020 16:08:01 -0800 Subject: [PATCH 08/30] [subset] Add a basic priority queue datastructure (binary heap). --- src/Makefile.am | 6 +- src/Makefile.sources | 1 + src/hb-priority-queue.hh | 149 +++++++++++++++++++++++++++++++++++++ src/test-priority-queue.cc | 89 ++++++++++++++++++++++ 4 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 src/hb-priority-queue.hh create mode 100644 src/test-priority-queue.cc diff --git a/src/Makefile.am b/src/Makefile.am index 20e9be32f..7a0ca2985 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -342,7 +342,7 @@ test_gsub_would_substitute_SOURCES = test-gsub-would-substitute.cc test_gsub_would_substitute_CPPFLAGS = $(HBCFLAGS) $(FREETYPE_CFLAGS) test_gsub_would_substitute_LDADD = libharfbuzz.la $(HBLIBS) $(FREETYPE_LIBS) -COMPILED_TESTS = test-algs test-array test-iter test-meta test-number test-ot-tag test-unicode-ranges test-bimap test-repacker +COMPILED_TESTS = test-algs test-array test-iter test-meta test-number test-ot-tag test-priority-queue test-unicode-ranges test-bimap test-repacker COMPILED_TESTS_CPPFLAGS = $(HBCFLAGS) -DMAIN -UNDEBUG COMPILED_TESTS_LDADD = libharfbuzz.la $(HBLIBS) check_PROGRAMS += $(COMPILED_TESTS) @@ -356,6 +356,10 @@ test_array_SOURCES = test-array.cc test_array_CPPFLAGS = $(HBCFLAGS) test_array_LDADD = libharfbuzz.la $(HBLIBS) +test_priority_queue_SOURCES = test-priority-queue.cc hb-static.cc +test_priority_queue_CPPFLAGS = $(HBCFLAGS) +test_priority_queue_LDADD = libharfbuzz.la $(HBLIBS) + test_repacker_SOURCES = test-repacker.cc hb-static.cc test_repacker_CPPFLAGS = $(HBCFLAGS) test_repacker_LDADD = libharfbuzz.la libharfbuzz-subset.la $(HBLIBS) diff --git a/src/Makefile.sources b/src/Makefile.sources index 933ae8850..da97530fe 100644 --- a/src/Makefile.sources +++ b/src/Makefile.sources @@ -167,6 +167,7 @@ HB_BASE_sources = \ hb-unicode.hh \ hb-utf.hh \ hb-vector.hh \ + hb-priority-queue.hh \ hb.hh \ $(NULL) diff --git a/src/hb-priority-queue.hh b/src/hb-priority-queue.hh new file mode 100644 index 000000000..6fd1a8d0b --- /dev/null +++ b/src/hb-priority-queue.hh @@ -0,0 +1,149 @@ +/* + * Copyright © 2020 Google, Inc. + * + * This is part of HarfBuzz, a text shaping library. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, provided that the + * above copyright notice and the following two paragraphs appear in + * all copies of this software. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES + * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN + * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * Google Author(s): Garret Rieger + */ + +#ifndef HB_PRIORITY_QUEUE_HH +#define HB_PRIORITY_QUEUE_HH + +#include "hb.hh" +#include "hb-vector.hh" + +/* + * hb_priority_queue_t + * + * Priority queue implemented as a binary heap. Supports extract minimum + * and insert operations. + */ +struct hb_priority_queue_t +{ + HB_DELETE_COPY_ASSIGN (hb_priority_queue_t); + hb_priority_queue_t () { init (); } + ~hb_priority_queue_t () { fini (); } + + private: + typedef hb_pair_t item_t; + hb_vector_t heap; + + public: + void init () { heap.init (); } + + void fini () { heap.fini (); } + + void reset () { heap.resize (0); } + + bool in_error () const { return heap.in_error (); } + + void insert (unsigned value, unsigned priority) + { + heap.push (item_t (value, priority)); + bubble_up (heap.length - 1); + } + + item_t extract_minimum () + { + item_t result = heap[0]; + + heap[0] = heap[heap.length - 1]; + heap.shrink (heap.length - 1); + bubble_down (0); + + return result; + } + + const item_t& minimum () + { + return heap[0]; + } + + bool is_empty () const { return heap.length == 0; } + + /* Sink interface. */ + hb_priority_queue_t& operator << (item_t item) + { insert (item.first, item.second); return *this; } + + private: + + unsigned parent (unsigned index) + { + return (index - 1) / 2; + } + + unsigned left_child (unsigned index) + { + return 2 * index + 1; + } + + unsigned right_child (unsigned index) + { + return 2 * index + 2; + } + + void bubble_down (unsigned index) + { + unsigned left = left_child (index); + unsigned right = right_child (index); + + bool has_left = left < heap.length; + if (!has_left) + // If there's no left, then there's also no right. + return; + + bool has_right = right < heap.length; + if (heap[index] <= heap[left] + && (!has_right || heap[index] <= heap[right])) + return; + + if (!has_right || heap[left] < heap[right]) + { + swap (index, left); + bubble_down (left); + return; + } + + swap (index, right); + bubble_down (right); + } + + void bubble_up (unsigned index) + { + if (index == 0) return; + + unsigned parent_index = parent (index); + if (heap[parent_index].second <= heap[index].second) + return; + + swap (index, parent_index); + bubble_up (parent_index); + } + + void swap (unsigned a, unsigned b) + { + item_t temp = heap[a]; + heap[a] = heap[b]; + heap[b] = temp; + } +}; + +#endif /* HB_PRIORITY_QUEUE_HH */ diff --git a/src/test-priority-queue.cc b/src/test-priority-queue.cc new file mode 100644 index 000000000..65c2a320b --- /dev/null +++ b/src/test-priority-queue.cc @@ -0,0 +1,89 @@ +/* + * Copyright © 2020 Google, Inc. + * + * This is part of HarfBuzz, a text shaping library. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, provided that the + * above copyright notice and the following two paragraphs appear in + * all copies of this software. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES + * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN + * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * Google Author(s): Garret Rieger + */ + +#include "hb.hh" +#include "hb-priority-queue.hh" + +static void +test_insert () +{ + hb_priority_queue_t queue; + assert (queue.is_empty ()); + + queue.insert (0, 10); + assert (!queue.is_empty ()); + assert (queue.minimum () == hb_pair (0, 10)); + + queue.insert (1, 20); + assert (queue.minimum () == hb_pair (0, 10)); + + queue.insert (2, 5); + assert (queue.minimum () == hb_pair (2, 5)); + + queue.insert (3, 15); + assert (queue.minimum () == hb_pair (2, 5)); + + queue.insert (4, 1); + assert (queue.minimum () == hb_pair (4, 1)); +} + +static void +test_extract () +{ + hb_priority_queue_t queue; + queue.insert (0, 0); + queue.insert (6, 60); + queue.insert (3, 30); + queue.insert (4, 40); + queue.insert (2, 20); + queue.insert (5, 50); + queue.insert (7, 70); + queue.insert (1, 10); + + for (int i = 0; i < 8; i++) + { + assert (!queue.is_empty ()); + assert (queue.minimum () == hb_pair (i, i * 10)); + assert (queue.extract_minimum () == hb_pair (i, i * 10)); + } + + assert (queue.is_empty ()); +} + +static void +test_extract_empty () +{ + hb_priority_queue_t queue; + assert (queue.extract_minimum () == hb_pair (0, 0)); +} + +int +main (int argc, char **argv) +{ + test_insert (); + test_extract (); + test_extract_empty (); +} From 4c8dd41ed90292d4516c539be23c16d625d69a41 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 5 Nov 2020 09:21:25 -0800 Subject: [PATCH 09/30] [subset] re-write compute distances to use an array lookup for the distance map. --- src/hb-priority-queue.hh | 4 +-- src/hb-repacker.hh | 55 +++++++++++++++++----------------------- src/test-repacker.cc | 2 ++ 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/src/hb-priority-queue.hh b/src/hb-priority-queue.hh index 6fd1a8d0b..68b17fdc6 100644 --- a/src/hb-priority-queue.hh +++ b/src/hb-priority-queue.hh @@ -43,7 +43,7 @@ struct hb_priority_queue_t ~hb_priority_queue_t () { fini (); } private: - typedef hb_pair_t item_t; + typedef hb_pair_t item_t; hb_vector_t heap; public: @@ -55,7 +55,7 @@ struct hb_priority_queue_t bool in_error () const { return heap.in_error (); } - void insert (unsigned value, unsigned priority) + void insert (unsigned value, int64_t priority) { heap.push (item_t (value, priority)); bubble_up (heap.length - 1); diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index 90d73b30c..1eebdd137 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -161,7 +161,7 @@ struct graph_t return; } - hb_hashmap_t distance_to; + hb_vector_t distance_to; compute_distances (&distance_to); hb_set_t queue; @@ -242,15 +242,16 @@ struct graph_t private: unsigned closest_object (const hb_set_t& queue, - const hb_hashmap_t& distance_to) + const hb_vector_t distance_to) { + // TODO(garretrieger): use a priority queue. int64_t closest_distance = hb_int_max (int64_t); unsigned closest_index = -1; for (unsigned i : queue) { - if (distance_to.get (i) < closest_distance) + if (distance_to[i] < closest_distance) { - closest_distance = distance_to.get (i); + closest_distance = distance_to[i]; closest_index = i; } } @@ -262,51 +263,41 @@ struct graph_t * Finds the distance too each object in the graph * from the initial node. */ - void compute_distances (hb_hashmap_t* distance_to) + void compute_distances (hb_vector_t* distance_to) { // Uses Dijkstra's algorithm to find all of the shortest distances. // https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm - distance_to->clear (); + distance_to->resize (0); + distance_to->resize (objects_.length); + for (unsigned i = 0; i < objects_.length; i++) + (*distance_to)[i] = hb_int_max (int64_t); + (*distance_to)[objects_.length - 1] = 0; + hb_set_t unvisited; unvisited.add_range (0, objects_.length - 1); - unsigned current_idx = objects_.length - 1; - distance_to->set (current_idx, 0); - - while (unvisited.get_population ()) + while (!unvisited.is_empty ()) { - const auto& current = objects_[current_idx]; - int current_distance = (*distance_to)[current_idx]; + unsigned next_idx = closest_object (unvisited, *distance_to); + const auto& next = objects_[next_idx]; + int next_distance = (*distance_to)[next_idx]; + unvisited.del (next_idx); - for (const auto& link : current.links) + for (const auto& link : next.links) { if (!unvisited.has (link.objidx)) continue; const auto& child = objects_[link.objidx]; int64_t child_weight = child.tail - child.head + (!link.is_wide ? (1 << 16) : ((int64_t) 1 << 32)); - int64_t child_distance = current_distance + child_weight; + int64_t child_distance = next_distance + child_weight; - if (child_distance < distance_to->get (link.objidx)) - distance_to->set (link.objidx, child_distance); + if (child_distance < (*distance_to)[link.objidx]) + (*distance_to)[link.objidx] = child_distance; } - - unvisited.del (current_idx); - - // TODO(garretrieger): change this to use a priority queue. - int64_t smallest_distance = hb_int_max(int64_t); - for (hb_codepoint_t idx : unvisited) - { - if (distance_to->get (idx) < smallest_distance) - { - smallest_distance = distance_to->get (idx); - current_idx = idx; - } - } - - // TODO(garretrieger): this will trigger if graph is disconnected. Handle this. - assert (!unvisited.get_population () || smallest_distance != hb_int_max (int64_t)); } + // TODO(garretrieger): Handle this. If anything is left, part of the graph is disconnected. + assert (unvisited.is_empty ()); } int64_t compute_offset ( diff --git a/src/test-repacker.cc b/src/test-repacker.cc index 46554e1cd..a070cddaf 100644 --- a/src/test-repacker.cc +++ b/src/test-repacker.cc @@ -271,6 +271,8 @@ static void test_will_overflow_2 () assert (graph.will_overflow ()); } +// TODO(garretrieger): add a test(s) using a real font. + int main (int argc, char **argv) { From 59ac0a0d0a99e91a75d18a3884ae276309370997 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 5 Nov 2020 10:29:56 -0800 Subject: [PATCH 10/30] [subset] Use priority for comparison in heap. --- src/hb-priority-queue.hh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hb-priority-queue.hh b/src/hb-priority-queue.hh index 68b17fdc6..e47127edb 100644 --- a/src/hb-priority-queue.hh +++ b/src/hb-priority-queue.hh @@ -111,11 +111,11 @@ struct hb_priority_queue_t return; bool has_right = right < heap.length; - if (heap[index] <= heap[left] - && (!has_right || heap[index] <= heap[right])) + if (heap[index].second <= heap[left].second + && (!has_right || heap[index].second <= heap[right].second)) return; - if (!has_right || heap[left] < heap[right]) + if (!has_right || heap[left].second < heap[right].second) { swap (index, left); bubble_down (left); From 5d3511e5b13eb825ea9914aa2400cc040edef8a7 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 5 Nov 2020 10:34:26 -0800 Subject: [PATCH 11/30] [subset] Change compute_distances() to use a priority queue. --- src/hb-repacker.hh | 42 ++++++++++++++++++++++++++++++------------ src/meson.build | 1 + 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index 1eebdd137..2fa8030fb 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -29,6 +29,7 @@ #include "hb-open-type.hh" #include "hb-map.hh" +#include "hb-priority-queue.hh" #include "hb-serialize.hh" #include "hb-vector.hh" @@ -267,25 +268,39 @@ struct graph_t { // Uses Dijkstra's algorithm to find all of the shortest distances. // https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm + // + // Implementation Note: + // Since our priority queue doesn't support fast priority decreases + // we instead just add new entries into the queue when a priority changes. + // Redundant ones are filtered out later on by the visited set. + // According to https://www3.cs.stonybrook.edu/~rezaul/papers/TR-07-54.pdf + // for practical performance this is faster then using a more advanced queue + // (such as a fibonaacci queue) with a fast decrease priority. + hb_priority_queue_t queue; distance_to->resize (0); distance_to->resize (objects_.length); for (unsigned i = 0; i < objects_.length; i++) - (*distance_to)[i] = hb_int_max (int64_t); - (*distance_to)[objects_.length - 1] = 0; - - hb_set_t unvisited; - unvisited.add_range (0, objects_.length - 1); - - while (!unvisited.is_empty ()) { - unsigned next_idx = closest_object (unvisited, *distance_to); + if (i == objects_.length - 1) + (*distance_to)[i] = 0; + else + (*distance_to)[i] = hb_int_max (int64_t); + queue.insert (i, (*distance_to)[i]); + } + + hb_set_t visited; + + while (!queue.is_empty ()) + { + unsigned next_idx = queue.extract_minimum ().first; + if (visited.has (next_idx)) continue; const auto& next = objects_[next_idx]; - int next_distance = (*distance_to)[next_idx]; - unvisited.del (next_idx); + int64_t next_distance = (*distance_to)[next_idx]; + visited.add (next_idx); for (const auto& link : next.links) { - if (!unvisited.has (link.objidx)) continue; + if (visited.has (link.objidx)) continue; const auto& child = objects_[link.objidx]; int64_t child_weight = child.tail - child.head + @@ -293,11 +308,14 @@ struct graph_t int64_t child_distance = next_distance + child_weight; if (child_distance < (*distance_to)[link.objidx]) + { (*distance_to)[link.objidx] = child_distance; + queue.insert (link.objidx, child_distance); + } } } // TODO(garretrieger): Handle this. If anything is left, part of the graph is disconnected. - assert (unvisited.is_empty ()); + assert (queue.is_empty ()); } int64_t compute_offset ( diff --git a/src/meson.build b/src/meson.build index ec3b7c718..280ddb521 100644 --- a/src/meson.build +++ b/src/meson.build @@ -477,6 +477,7 @@ if get_option('tests').enabled() compiled_tests = { 'test-algs': ['test-algs.cc', 'hb-static.cc'], 'test-array': ['test-array.cc'], + 'test-repacker': ['test-repacker.cc', 'hb-static.cc'], 'test-iter': ['test-iter.cc', 'hb-static.cc'], 'test-meta': ['test-meta.cc', 'hb-static.cc'], 'test-number': ['test-number.cc', 'hb-number.cc'], From 519ae96617e1e2867122c5dbbdc8f1bbce89cb24 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 5 Nov 2020 11:22:16 -0800 Subject: [PATCH 12/30] [subset] switch sort_shortest_distance() to use priority queue. --- src/hb-repacker.hh | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index 2fa8030fb..efd400e49 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -165,7 +165,7 @@ struct graph_t hb_vector_t distance_to; compute_distances (&distance_to); - hb_set_t queue; + hb_priority_queue_t queue; hb_vector_t sorted_graph; hb_map_t id_map; hb_map_t edge_count; @@ -174,13 +174,12 @@ struct graph_t // Object graphs are in reverse order, the first object is at the end // of the vector. Since the graph is topologically sorted it's safe to // assume the first object has no incoming edges. - queue.add (objects_.length - 1); + queue.insert (objects_.length - 1, add_order(distance_to[objects_.length - 1], 0)); int new_id = objects_.length - 1; - - while (queue.get_population ()) + unsigned order = 1; + while (!queue.is_empty ()) { - unsigned next_id = closest_object (queue, distance_to); - queue.del (next_id); + unsigned next_id = queue.extract_minimum().first; hb_serialize_context_t::object_t& next = objects_[next_id]; sorted_graph.push (next); @@ -189,7 +188,12 @@ struct graph_t for (const auto& link : next.links) { edge_count.set (link.objidx, edge_count.get (link.objidx) - 1); if (!edge_count.get (link.objidx)) - queue.add (link.objidx); + // Add the order that the links were encountered to the priority. + // This ensures that ties between priorities objects are broken in a consistent + // way. More specifically this is set up so that if a set of objects have the same + // distance they'll be added to the topolical order in the order that they are + // referenced from the parent object. + queue.insert (link.objidx, add_order(distance_to[link.objidx], order++)); } } @@ -242,22 +246,9 @@ struct graph_t private: - unsigned closest_object (const hb_set_t& queue, - const hb_vector_t distance_to) + int64_t add_order (int64_t distance, unsigned order) { - // TODO(garretrieger): use a priority queue. - int64_t closest_distance = hb_int_max (int64_t); - unsigned closest_index = -1; - for (unsigned i : queue) - { - if (distance_to[i] < closest_distance) - { - closest_distance = distance_to[i]; - closest_index = i; - } - } - assert (closest_index != (unsigned) -1); - return closest_index; + return (distance << 24) | (0x00FFFFFF & order); } /* From 8286bd80940a7d136ee503dd1b1142190c6695ff Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 5 Nov 2020 14:23:29 -0800 Subject: [PATCH 13/30] [subset] use vectors instead of hashmaps throughout the repacker since all keys will be mapped for these use cases. --- src/hb-repacker.hh | 65 ++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index efd400e49..e48949481 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -110,8 +110,9 @@ struct graph_t hb_vector_t queue; hb_vector_t sorted_graph; - hb_map_t id_map; - hb_map_t edge_count; + hb_vector_t id_map; + id_map.resize (objects_.length); + hb_vector_t edge_count; incoming_edge_count (&edge_count); // Object graphs are in reverse order, the first object is at the end @@ -127,12 +128,12 @@ struct graph_t hb_serialize_context_t::object_t& next = objects_[next_id]; sorted_graph.push (next); - id_map.set (next_id, new_id--); + id_map[next_id] = new_id--; for (const auto& link : next.links) { // TODO(garretrieger): sort children from smallest to largest - edge_count.set (link.objidx, edge_count.get (link.objidx) - 1); - if (!edge_count.get (link.objidx)) + edge_count[link.objidx] -= 1; + if (!edge_count[link.objidx]) queue.push (link.objidx); } } @@ -167,8 +168,9 @@ struct graph_t hb_priority_queue_t queue; hb_vector_t sorted_graph; - hb_map_t id_map; - hb_map_t edge_count; + hb_vector_t id_map; + id_map.resize (objects_.length); + hb_vector_t edge_count; incoming_edge_count (&edge_count); // Object graphs are in reverse order, the first object is at the end @@ -183,11 +185,11 @@ struct graph_t hb_serialize_context_t::object_t& next = objects_[next_id]; sorted_graph.push (next); - id_map.set (next_id, new_id--); + id_map[next_id] = new_id--; for (const auto& link : next.links) { - edge_count.set (link.objidx, edge_count.get (link.objidx) - 1); - if (!edge_count.get (link.objidx)) + edge_count[link.objidx] -= 1; + if (!edge_count[link.objidx]) // Add the order that the links were encountered to the priority. // This ensures that ties between priorities objects are broken in a consistent // way. More specifically this is set up so that if a set of objects have the same @@ -216,15 +218,17 @@ struct graph_t */ bool will_overflow () { - hb_map_t start_positions; - hb_map_t end_positions; + hb_vector_t start_positions; + start_positions.resize (objects_.length); + hb_vector_t end_positions; + end_positions.resize (objects_.length); unsigned current_pos = 0; for (int i = objects_.length - 1; i >= 0; i--) { - start_positions.set (i, current_pos); + start_positions[i] = current_pos; current_pos += objects_[i].tail - objects_[i].head; - end_positions.set (i, current_pos); + end_positions[i] = current_pos; } @@ -267,7 +271,6 @@ struct graph_t // According to https://www3.cs.stonybrook.edu/~rezaul/papers/TR-07-54.pdf // for practical performance this is faster then using a more advanced queue // (such as a fibonaacci queue) with a fast decrease priority. - hb_priority_queue_t queue; distance_to->resize (0); distance_to->resize (objects_.length); for (unsigned i = 0; i < objects_.length; i++) @@ -276,9 +279,11 @@ struct graph_t (*distance_to)[i] = 0; else (*distance_to)[i] = hb_int_max (int64_t); - queue.insert (i, (*distance_to)[i]); } + hb_priority_queue_t queue; + queue.insert (objects_.length - 1, 0); + hb_set_t visited; while (!queue.is_empty ()) @@ -312,8 +317,8 @@ struct graph_t int64_t compute_offset ( unsigned parent_idx, const hb_serialize_context_t::object_t::link_t& link, - const hb_map_t& start_positions, - const hb_map_t& end_positions) + const hb_vector_t& start_positions, + const hb_vector_t& end_positions) { unsigned child_idx = link.objidx; int64_t offset = 0; @@ -353,7 +358,7 @@ struct graph_t /* * Updates all objidx's in all links using the provided mapping. */ - void remap_obj_indices (const hb_map_t& id_map, + void remap_obj_indices (const hb_vector_t& id_map, hb_vector_t* sorted_graph) { for (unsigned i = 0; i < sorted_graph->length; i++) @@ -361,10 +366,7 @@ struct graph_t for (unsigned j = 0; j < (*sorted_graph)[i].links.length; j++) { auto& link = (*sorted_graph)[i].links[j]; - if (!id_map.has (link.objidx)) - // TODO(garretrieger): handle this. - assert (false); - link.objidx = id_map.get (link.objidx); + link.objidx = id_map[link.objidx]; } } } @@ -372,20 +374,15 @@ struct graph_t /* * Creates a map from objid to # of incoming edges. */ - void incoming_edge_count (hb_map_t* out) + void incoming_edge_count (hb_vector_t* out) { - for (unsigned i = 0; i < objects_.length; i++) + out->resize (0); + out->resize (objects_.length); + for (const auto& o : objects_) { - if (!out->has (i)) - out->set (i, 0); - - for (const auto& l : objects_[i].links) + for (const auto& l : o.links) { - unsigned id = l.objidx; - if (out->has (id)) - out->set (id, out->get (id) + 1); - else - out->set (id, 1); + (*out)[l.objidx] += 1; } } } From 75414e82b52d5a3adeb1eb48e64f43472913cba2 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 5 Nov 2020 16:39:23 -0800 Subject: [PATCH 14/30] [subset] Add table duplication overflow resolution. --- src/hb-debug.hh | 4 + src/hb-repacker.hh | 181 +++++++++++++++++++++++++++++++++------ src/test-repacker.cc | 196 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 347 insertions(+), 34 deletions(-) diff --git a/src/hb-debug.hh b/src/hb-debug.hh index ec3a1ff21..a92614d01 100644 --- a/src/hb-debug.hh +++ b/src/hb-debug.hh @@ -438,6 +438,10 @@ struct hb_no_trace_t { #define TRACE_SUBSET(this) hb_no_trace_t trace #endif +#ifndef HB_DEBUG_SUBSET_REPACK +#define HB_DEBUG_SUBSET_REPACK (HB_DEBUG+0) +#endif + #ifndef HB_DEBUG_DISPATCH #define HB_DEBUG_DISPATCH ( \ HB_DEBUG_APPLY + \ diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index e48949481..83a4db715 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -39,6 +39,36 @@ struct graph_t // TODO(garretrieger): add an error tracking system similar to what serialize_context_t // does. + struct overflow_record_t + { + unsigned parent; + const hb_serialize_context_t::object_t::link_t* link; + }; + + struct clone_buffer_t + { + clone_buffer_t () : head (nullptr), tail (nullptr) {} + + void copy (const hb_serialize_context_t::object_t& object) + { + fini (); + unsigned size = object.tail - object.head; + head = (char*) malloc (size); + memcpy (head, object.head, size); + tail = head + size; + } + + char* head; + char* tail; + + void fini () + { + if (!head) return; + free (head); + head = nullptr; + } + }; + /* * A topological sorting of an object graph. Ordered * in reverse serialization order (first object in the @@ -72,6 +102,7 @@ struct graph_t ~graph_t () { objects_.fini_deep (); + clone_buffers_.fini_deep (); } /* @@ -213,11 +244,45 @@ struct graph_t sorted_graph.fini_deep (); } + /* + * Creates a copy of child and re-assigns the link from + * parent to the clone. The copy is a shallow copy, objects + * linked from child are not duplicated. + */ + void duplicate (unsigned parent_idx, unsigned child_idx) + { + const auto& child = objects_[child_idx]; + clone_buffer_t* buffer = clone_buffers_.push (); + buffer->copy (child); + + auto* clone = objects_.push (); + clone->head = buffer->head; + clone->tail = buffer->tail; + for (const auto& l : child.links) + clone->links.push (l); + + auto& parent = objects_[parent_idx]; + unsigned clone_idx = objects_.length - 2; + for (unsigned i = 0; i < parent.links.length; i++) + { + auto& l = parent.links[i]; + if (l.objidx == child_idx) l.objidx = clone_idx; + } + + // The last object is the root of the graph, so swap back the root to the end. + // The root's obj idx does change, however since it's root nothing else refers to it. + // all other obj idx's will be unaffected. + hb_serialize_context_t::object_t root = objects_[objects_.length - 2]; + objects_[objects_.length - 2] = *clone; + objects_[objects_.length - 1] = root; + } + /* * Will any offsets overflow on graph when it's serialized? */ - bool will_overflow () + bool will_overflow (hb_vector_t* overflows) { + if (overflows) overflows->resize (0); hb_vector_t start_positions; start_positions.resize (objects_.length); hb_vector_t end_positions; @@ -232,7 +297,7 @@ struct graph_t } - for (unsigned parent_idx = 0; parent_idx < objects_.length; parent_idx++) + for (int parent_idx = objects_.length - 1; parent_idx >= 0; parent_idx--) { for (const auto& link : objects_[parent_idx].links) { @@ -241,11 +306,52 @@ struct graph_t start_positions, end_positions); - if (!is_valid_offset (offset, link)) return true; + if (is_valid_offset (offset, link)) + continue; + + if (!overflows) return true; + + overflow_record_t r; + r.parent = parent_idx; + r.link = &link; + overflows->push (r); } } - return false; + if (!overflows) return false; + return overflows->length; + } + + /* + * Creates a map from objid to # of incoming edges. + */ + void incoming_edge_count (hb_vector_t* out) const + { + out->resize (0); + out->resize (objects_.length); + for (const auto& o : objects_) + { + for (const auto& l : o.links) + { + (*out)[l.objidx] += 1; + } + } + } + + void print_overflows (const hb_vector_t& overflows) const + { + if (!DEBUG_ENABLED(SUBSET_REPACK)) return; + + hb_vector_t edge_count; + incoming_edge_count (&edge_count); + for (const auto& o : overflows) + { + DEBUG_MSG (SUBSET_REPACK, nullptr, "overflow from %d => %d (%d incoming , %d outgoing)", + o.parent, + o.link->objidx, + edge_count[o.link->objidx], + objects_[o.link->objidx].links.length); + } } private: @@ -371,22 +477,6 @@ struct graph_t } } - /* - * Creates a map from objid to # of incoming edges. - */ - void incoming_edge_count (hb_vector_t* out) - { - out->resize (0); - out->resize (objects_.length); - for (const auto& o : objects_) - { - for (const auto& l : o.links) - { - (*out)[l.objidx] += 1; - } - } - } - template void serialize_link_of_type (const hb_serialize_context_t::object_t::link_t& link, char* head, @@ -426,6 +516,7 @@ struct graph_t public: hb_vector_t objects_; + hb_vector_t clone_buffers_; }; @@ -438,13 +529,49 @@ hb_resolve_overflows (const hb_vector_t& pac hb_serialize_context_t* c) { graph_t sorted_graph (packed); sorted_graph.sort_kahn (); - if (sorted_graph.will_overflow ()) { - sorted_graph.sort_shortest_distance (); - // TODO(garretrieger): try additional offset resolution strategies - // - Dijkstra sort of weighted graph. - // - Promotion to extension lookups. - // - Table duplication. - // - Table splitting. + if (!sorted_graph.will_overflow (nullptr)) return; + + sorted_graph.sort_shortest_distance (); + + unsigned round = 0; + hb_vector_t overflows; + // TODO(garretrieger): select a good limit for max rounds. + while (sorted_graph.will_overflow (&overflows) && round++ < 10) { + DEBUG_MSG (SUBSET_REPACK, nullptr, "Over flow resolution round %d", round); + sorted_graph.print_overflows (overflows); + + // TODO(garretrieger): cache ege count in the graph object . Will need to be invalidated + // by graph modifications. + hb_vector_t edge_count; + sorted_graph.incoming_edge_count (&edge_count); + + // Try resolving the furthest overflow first. + bool resolution_attempted = false; + for (int i = overflows.length - 1; i >= 0; i--) + { + const graph_t::overflow_record_t& r = overflows[i]; + if (edge_count[r.link->objidx] > 1) + { + DEBUG_MSG (SUBSET_REPACK, nullptr, "Duplicating %d => %d", + r.parent, r.link->objidx); + // The child object is shared, we may be able to eliminate the overflow + // by duplicating it. + sorted_graph.duplicate (r.parent, r.link->objidx); + sorted_graph.sort_shortest_distance (); + resolution_attempted = true; + break; + } + + // TODO(garretrieger): add additional offset resolution strategies + // - Promotion to extension lookups. + // - Table splitting. + } + + if (!resolution_attempted) + { + DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :("); + break; + } } sorted_graph.serialize (c); diff --git a/src/test-repacker.cc b/src/test-repacker.cc index a070cddaf..e18cabdab 100644 --- a/src/test-repacker.cc +++ b/src/test-repacker.cc @@ -75,12 +75,12 @@ populate_serializer_simple (hb_serialize_context_t* c) static void populate_serializer_with_overflow (hb_serialize_context_t* c) { - std::string large_string(40000, 'a'); + std::string large_string(50000, 'a'); c->start_serialize (); - unsigned obj_1 = add_object (large_string.c_str(), 40000, c); - unsigned obj_2 = add_object (large_string.c_str(), 40000, c); - unsigned obj_3 = add_object (large_string.c_str(), 40000, c); + unsigned obj_1 = add_object (large_string.c_str(), 10000, c); + unsigned obj_2 = add_object (large_string.c_str(), 20000, c); + unsigned obj_3 = add_object (large_string.c_str(), 50000, c); start_object ("abc", 3, c); add_offset (obj_3, c); @@ -91,6 +91,26 @@ populate_serializer_with_overflow (hb_serialize_context_t* c) c->end_serialize(); } +static void +populate_serializer_with_dedup_overflow (hb_serialize_context_t* c) +{ + std::string large_string(70000, 'a'); + c->start_serialize (); + + unsigned obj_1 = add_object ("def", 3, c); + + start_object (large_string.c_str(), 60000, c); + add_offset (obj_1, c); + unsigned obj_2 = c->pop_pack (false); + + start_object (large_string.c_str(), 10000, c); + add_offset (obj_2, c); + add_offset (obj_1, c); + c->pop_pack (false); + + c->end_serialize(); +} + static void populate_serializer_complex_1 (hb_serialize_context_t* c) { @@ -116,7 +136,7 @@ populate_serializer_complex_2 (hb_serialize_context_t* c) { c->start_serialize (); - unsigned obj_5 = add_object ("mn", 3, c); + unsigned obj_5 = add_object ("mn", 2, c); unsigned obj_4 = add_object ("jkl", 3, c); @@ -137,6 +157,36 @@ populate_serializer_complex_2 (hb_serialize_context_t* c) c->end_serialize(); } +static void +populate_serializer_complex_3 (hb_serialize_context_t* c) +{ + c->start_serialize (); + + unsigned obj_6 = add_object ("opqrst", 6, c); + + unsigned obj_5 = add_object ("mn", 2, c); + + start_object ("jkl", 3, c); + add_offset (obj_6, c); + unsigned obj_4 = c->pop_pack (false); + + start_object ("ghi", 3, c); + add_offset (obj_4, c); + unsigned obj_3 = c->pop_pack (false); + + start_object ("def", 3, c); + add_offset (obj_3, c); + unsigned obj_2 = c->pop_pack (false); + + start_object ("abc", 3, c); + add_offset (obj_2, c); + add_offset (obj_4, c); + add_offset (obj_5, c); + c->pop_pack (); + + c->end_serialize(); +} + static void test_sort_kahn_1 () { size_t buffer_size = 100; @@ -227,6 +277,79 @@ static void test_sort_shortest () assert(graph.objects_[0].links.length == 0); } +static void test_duplicate_leaf () +{ + size_t buffer_size = 100; + void* buffer = malloc (buffer_size); + hb_serialize_context_t c (buffer, buffer_size); + populate_serializer_complex_2 (&c); + + graph_t graph (c.object_graph ()); + graph.duplicate (4, 1); + + assert(strncmp (graph.objects_[5].head, "abc", 3) == 0); + assert(graph.objects_[5].links.length == 3); + assert(graph.objects_[5].links[0].objidx == 3); + assert(graph.objects_[5].links[1].objidx == 4); + assert(graph.objects_[5].links[2].objidx == 0); + + assert(strncmp (graph.objects_[4].head, "jkl", 3) == 0); + assert(graph.objects_[4].links.length == 0); + + assert(strncmp (graph.objects_[3].head, "def", 3) == 0); + assert(graph.objects_[3].links.length == 1); + assert(graph.objects_[3].links[0].objidx == 2); + + assert(strncmp (graph.objects_[2].head, "ghi", 3) == 0); + assert(graph.objects_[2].links.length == 1); + assert(graph.objects_[2].links[0].objidx == 1); + + assert(strncmp (graph.objects_[1].head, "jkl", 3) == 0); + assert(graph.objects_[1].links.length == 0); + + assert(strncmp (graph.objects_[0].head, "mn", 2) == 0); + assert(graph.objects_[0].links.length == 0); +} + +static void test_duplicate_interior () +{ + size_t buffer_size = 100; + void* buffer = malloc (buffer_size); + hb_serialize_context_t c (buffer, buffer_size); + populate_serializer_complex_3 (&c); + + graph_t graph (c.object_graph ()); + graph.duplicate (3, 2); + + assert(strncmp (graph.objects_[6].head, "abc", 3) == 0); + assert(graph.objects_[6].links.length == 3); + assert(graph.objects_[6].links[0].objidx == 4); + assert(graph.objects_[6].links[1].objidx == 2); + assert(graph.objects_[6].links[2].objidx == 1); + + assert(strncmp (graph.objects_[5].head, "jkl", 3) == 0); + assert(graph.objects_[5].links.length == 1); + assert(graph.objects_[5].links[0].objidx == 0); + + assert(strncmp (graph.objects_[4].head, "def", 3) == 0); + assert(graph.objects_[4].links.length == 1); + assert(graph.objects_[4].links[0].objidx == 3); + + assert(strncmp (graph.objects_[3].head, "ghi", 3) == 0); + assert(graph.objects_[3].links.length == 1); + assert(graph.objects_[3].links[0].objidx == 5); + + assert(strncmp (graph.objects_[2].head, "jkl", 3) == 0); + assert(graph.objects_[2].links.length == 1); + assert(graph.objects_[2].links[0].objidx == 0); + + assert(strncmp (graph.objects_[1].head, "mn", 2) == 0); + assert(graph.objects_[1].links.length == 0); + + assert(strncmp (graph.objects_[0].head, "opqrst", 6) == 0); + assert(graph.objects_[0].links.length == 0); +} + static void test_serialize () { @@ -257,7 +380,7 @@ static void test_will_overflow_1 () populate_serializer_complex_2 (&c); graph_t graph (c.object_graph ()); - assert (!graph.will_overflow ()); + assert (!graph.will_overflow (nullptr)); } static void test_will_overflow_2 () @@ -268,9 +391,63 @@ static void test_will_overflow_2 () populate_serializer_with_overflow (&c); graph_t graph (c.object_graph ()); - assert (graph.will_overflow ()); + assert (graph.will_overflow (nullptr)); } +static void test_will_overflow_3 () +{ + size_t buffer_size = 160000; + void* buffer = malloc (buffer_size); + hb_serialize_context_t c (buffer, buffer_size); + populate_serializer_with_dedup_overflow (&c); + graph_t graph (c.object_graph ()); + + assert (graph.will_overflow (nullptr)); +} + +static void test_resolve_overflows_via_sort () +{ + size_t buffer_size = 160000; + void* buffer = malloc (buffer_size); + hb_serialize_context_t c (buffer, buffer_size); + populate_serializer_with_overflow (&c); + graph_t graph (c.object_graph ()); + + void* out_buffer = malloc (buffer_size); + hb_serialize_context_t out (out_buffer, buffer_size); + + hb_resolve_overflows (c.object_graph (), &out); + assert (!out.offset_overflow); + hb_bytes_t result = out.copy_bytes (); + assert (result.length == (80000 + 3 + 3 * 2)); + + result.free (); + free (buffer); + free (out_buffer); +} + +static void test_resolve_overflows_via_duplication () +{ + size_t buffer_size = 160000; + void* buffer = malloc (buffer_size); + hb_serialize_context_t c (buffer, buffer_size); + populate_serializer_with_dedup_overflow (&c); + graph_t graph (c.object_graph ()); + + void* out_buffer = malloc (buffer_size); + hb_serialize_context_t out (out_buffer, buffer_size); + + hb_resolve_overflows (c.object_graph (), &out); + assert (!out.offset_overflow); + hb_bytes_t result = out.copy_bytes (); + assert (result.length == (10000 + 2 * 2 + 60000 + 2 + 3 * 2)); + + result.free (); + free (buffer); + free (out_buffer); +} + +// TODO(garretrieger): update will_overflow tests to check the overflows array. // TODO(garretrieger): add a test(s) using a real font. int @@ -282,4 +459,9 @@ main (int argc, char **argv) test_sort_shortest (); test_will_overflow_1 (); test_will_overflow_2 (); + test_will_overflow_3 (); + test_resolve_overflows_via_sort (); + test_resolve_overflows_via_duplication (); + test_duplicate_leaf (); + test_duplicate_interior (); } From b452b2c76c9f76c9ea3501e8eb6534cb172f59ce Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Fri, 6 Nov 2020 15:37:05 -0800 Subject: [PATCH 15/30] [subset] refactor repacker graph to cache edge count and distances of vertices. --- src/hb-repacker.hh | 315 +++++++++++++++++++++++++------------------ src/test-repacker.cc | 158 +++++++++++----------- 2 files changed, 265 insertions(+), 208 deletions(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index 83a4db715..d6f05c1f1 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -39,6 +39,17 @@ struct graph_t // TODO(garretrieger): add an error tracking system similar to what serialize_context_t // does. + struct vertex_t + { + void fini () { obj.fini (); } + + hb_serialize_context_t::object_t obj; + int64_t distance; + unsigned incoming_edges; + unsigned start; + unsigned end; + }; + struct overflow_record_t { unsigned parent; @@ -77,6 +88,9 @@ struct graph_t * serializer */ graph_t (const hb_vector_t& objects) + : edge_count_invalid (true), + distance_invalid (true), + positions_invalid (true) { bool removed_nil = false; for (unsigned i = 0; i < objects.length; i++) @@ -91,38 +105,59 @@ struct graph_t continue; } - auto* copy = objects_.push (*objects[i]); + vertex_t* v = vertices_.push (); + v->obj = *objects[i]; if (!removed_nil) continue; - for (unsigned i = 0; i < copy->links.length; i++) + for (unsigned i = 0; i < v->obj.links.length; i++) // Fix indices to account for removed nil object. - copy->links[i].objidx--; + v->obj.links[i].objidx--; } } ~graph_t () { - objects_.fini_deep (); + vertices_.fini_deep (); clone_buffers_.fini_deep (); } + const vertex_t& root () const + { + return vertices_[root_idx ()]; + } + + unsigned root_idx () const + { + // Object graphs are in reverse order, the first object is at the end + // of the vector. Since the graph is topologically sorted it's safe to + // assume the first object has no incoming edges. + return vertices_.length - 1; + } + + const hb_serialize_context_t::object_t& object(unsigned i) const + { + return vertices_[i].obj; + } + /* * serialize graph into the provided serialization buffer. */ - void serialize (hb_serialize_context_t* c) + void serialize (hb_serialize_context_t* c) const { c->start_serialize (); - for (unsigned i = 0; i < objects_.length; i++) { + for (unsigned i = 0; i < vertices_.length; i++) { c->push (); - size_t size = objects_[i].tail - objects_[i].head; + size_t size = vertices_[i].obj.tail - vertices_[i].obj.head; char* start = c->allocate_size (size); if (!start) return; - memcpy (start, objects_[i].head, size); + memcpy (start, vertices_[i].obj.head, size); - for (const auto& link : objects_[i].links) + for (const auto& link : vertices_[i].obj.links) serialize_link (link, start, c); + // All duplications are already encoded in the graph, so don't + // enable sharing during packing. c->pop_pack (false); } c->end_serialize (); @@ -134,37 +169,38 @@ struct graph_t */ void sort_kahn () { - if (objects_.length <= 1) { + positions_invalid = true; + + if (vertices_.length <= 1) { // Graph of 1 or less doesn't need sorting. return; } hb_vector_t queue; - hb_vector_t sorted_graph; + hb_vector_t sorted_graph; hb_vector_t id_map; - id_map.resize (objects_.length); - hb_vector_t edge_count; - incoming_edge_count (&edge_count); + id_map.resize (vertices_.length); - // Object graphs are in reverse order, the first object is at the end - // of the vector. Since the graph is topologically sorted it's safe to - // assume the first object has no incoming edges. - queue.push (objects_.length - 1); - int new_id = objects_.length - 1; + hb_vector_t removed_edges; + removed_edges.resize (vertices_.length); + update_incoming_edge_count (); + + queue.push (root_idx ()); + int new_id = vertices_.length - 1; while (queue.length) { unsigned next_id = queue[0]; queue.remove(0); - hb_serialize_context_t::object_t& next = objects_[next_id]; + vertex_t& next = vertices_[next_id]; sorted_graph.push (next); id_map[next_id] = new_id--; - for (const auto& link : next.links) { + for (const auto& link : next.obj.links) { // TODO(garretrieger): sort children from smallest to largest - edge_count[link.objidx] -= 1; - if (!edge_count[link.objidx]) + removed_edges[link.objidx]++; + if (!(vertices_[link.objidx].incoming_edges - removed_edges[link.objidx])) queue.push (link.objidx); } } @@ -179,7 +215,7 @@ struct graph_t remap_obj_indices (id_map, &sorted_graph); sorted_graph.as_array ().reverse (); - objects_ = sorted_graph; + vertices_ = sorted_graph; sorted_graph.fini_deep (); } @@ -189,44 +225,47 @@ struct graph_t */ void sort_shortest_distance () { - if (objects_.length <= 1) { + positions_invalid = true; + + if (vertices_.length <= 1) { // Graph of 1 or less doesn't need sorting. return; } - hb_vector_t distance_to; - compute_distances (&distance_to); + update_distances (); hb_priority_queue_t queue; - hb_vector_t sorted_graph; + hb_vector_t sorted_graph; hb_vector_t id_map; - id_map.resize (objects_.length); - hb_vector_t edge_count; - incoming_edge_count (&edge_count); + id_map.resize (vertices_.length); + + hb_vector_t removed_edges; + removed_edges.resize (vertices_.length); + update_incoming_edge_count (); // Object graphs are in reverse order, the first object is at the end // of the vector. Since the graph is topologically sorted it's safe to // assume the first object has no incoming edges. - queue.insert (objects_.length - 1, add_order(distance_to[objects_.length - 1], 0)); - int new_id = objects_.length - 1; + queue.insert (root_idx (), add_order(root ().distance, 0)); + int new_id = root_idx (); unsigned order = 1; while (!queue.is_empty ()) { unsigned next_id = queue.extract_minimum().first; - hb_serialize_context_t::object_t& next = objects_[next_id]; + vertex_t& next = vertices_[next_id]; sorted_graph.push (next); id_map[next_id] = new_id--; - for (const auto& link : next.links) { - edge_count[link.objidx] -= 1; - if (!edge_count[link.objidx]) + for (const auto& link : next.obj.links) { + removed_edges[link.objidx]++; + if (!(vertices_[link.objidx].incoming_edges - removed_edges[link.objidx])) // Add the order that the links were encountered to the priority. // This ensures that ties between priorities objects are broken in a consistent // way. More specifically this is set up so that if a set of objects have the same // distance they'll be added to the topolical order in the order that they are // referenced from the parent object. - queue.insert (link.objidx, add_order(distance_to[link.objidx], order++)); + queue.insert (link.objidx, add_order(vertices_[link.objidx].distance, order++)); } } @@ -240,7 +279,7 @@ struct graph_t remap_obj_indices (id_map, &sorted_graph); sorted_graph.as_array ().reverse (); - objects_ = sorted_graph; + vertices_ = sorted_graph; sorted_graph.fini_deep (); } @@ -251,30 +290,39 @@ struct graph_t */ void duplicate (unsigned parent_idx, unsigned child_idx) { - const auto& child = objects_[child_idx]; + positions_invalid = true; + + auto& child = vertices_[child_idx]; clone_buffer_t* buffer = clone_buffers_.push (); - buffer->copy (child); + buffer->copy (child.obj); - auto* clone = objects_.push (); - clone->head = buffer->head; - clone->tail = buffer->tail; - for (const auto& l : child.links) - clone->links.push (l); + auto* clone = vertices_.push (); + clone->obj.head = buffer->head; + clone->obj.tail = buffer->tail; + clone->distance = child.distance; - auto& parent = objects_[parent_idx]; - unsigned clone_idx = objects_.length - 2; - for (unsigned i = 0; i < parent.links.length; i++) + for (const auto& l : child.obj.links) + clone->obj.links.push (l); + + auto& parent = vertices_[parent_idx]; + unsigned clone_idx = vertices_.length - 2; + for (unsigned i = 0; i < parent.obj.links.length; i++) { - auto& l = parent.links[i]; - if (l.objidx == child_idx) l.objidx = clone_idx; + auto& l = parent.obj.links[i]; + if (l.objidx == child_idx) + { + l.objidx = clone_idx; + clone->incoming_edges++; + child.incoming_edges--; + } } // The last object is the root of the graph, so swap back the root to the end. // The root's obj idx does change, however since it's root nothing else refers to it. // all other obj idx's will be unaffected. - hb_serialize_context_t::object_t root = objects_[objects_.length - 2]; - objects_[objects_.length - 2] = *clone; - objects_[objects_.length - 1] = root; + vertex_t root = vertices_[vertices_.length - 2]; + vertices_[vertices_.length - 2] = *clone; + vertices_[vertices_.length - 1] = root; } /* @@ -283,29 +331,13 @@ struct graph_t bool will_overflow (hb_vector_t* overflows) { if (overflows) overflows->resize (0); - hb_vector_t start_positions; - start_positions.resize (objects_.length); - hb_vector_t end_positions; - end_positions.resize (objects_.length); + update_positions (); - unsigned current_pos = 0; - for (int i = objects_.length - 1; i >= 0; i--) + for (int parent_idx = vertices_.length - 1; parent_idx >= 0; parent_idx--) { - start_positions[i] = current_pos; - current_pos += objects_[i].tail - objects_[i].head; - end_positions[i] = current_pos; - } - - - for (int parent_idx = objects_.length - 1; parent_idx >= 0; parent_idx--) - { - for (const auto& link : objects_[parent_idx].links) + for (const auto& link : vertices_[parent_idx].obj.links) { - int64_t offset = compute_offset (parent_idx, - link, - start_positions, - end_positions); - + int64_t offset = compute_offset (parent_idx, link); if (is_valid_offset (offset, link)) continue; @@ -322,51 +354,78 @@ struct graph_t return overflows->length; } - /* - * Creates a map from objid to # of incoming edges. - */ - void incoming_edge_count (hb_vector_t* out) const - { - out->resize (0); - out->resize (objects_.length); - for (const auto& o : objects_) - { - for (const auto& l : o.links) - { - (*out)[l.objidx] += 1; - } - } - } - - void print_overflows (const hb_vector_t& overflows) const + void print_overflows (const hb_vector_t& overflows) { if (!DEBUG_ENABLED(SUBSET_REPACK)) return; - hb_vector_t edge_count; - incoming_edge_count (&edge_count); + update_incoming_edge_count (); for (const auto& o : overflows) { + const auto& child = vertices_[o.link->objidx]; DEBUG_MSG (SUBSET_REPACK, nullptr, "overflow from %d => %d (%d incoming , %d outgoing)", o.parent, o.link->objidx, - edge_count[o.link->objidx], - objects_[o.link->objidx].links.length); + child.incoming_edges, + child.obj.links.length); } } private: + /* + * Creates a map from objid to # of incoming edges. + */ + void update_incoming_edge_count () + { + if (!edge_count_invalid) return; + + for (unsigned i = 0; i < vertices_.length; i++) + vertices_[i].incoming_edges = 0; + + for (const vertex_t& v : vertices_) + { + for (auto& l : v.obj.links) + { + vertices_[l.objidx].incoming_edges++; + } + } + + edge_count_invalid = false; + } + + int64_t add_order (int64_t distance, unsigned order) { return (distance << 24) | (0x00FFFFFF & order); } + /* + * compute the serialized start and end positions for each vertex. + */ + void update_positions () + { + if (!positions_invalid) return; + + unsigned current_pos = 0; + for (int i = root_idx (); i >= 0; i--) + { + auto& v = vertices_[i]; + v.start = current_pos; + current_pos += v.obj.tail - v.obj.head; + v.end = current_pos; + } + + positions_invalid = false; + } + /* * Finds the distance too each object in the graph * from the initial node. */ - void compute_distances (hb_vector_t* distance_to) + void update_distances () { + if (!distance_invalid) return; + // Uses Dijkstra's algorithm to find all of the shortest distances. // https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm // @@ -377,18 +436,16 @@ struct graph_t // According to https://www3.cs.stonybrook.edu/~rezaul/papers/TR-07-54.pdf // for practical performance this is faster then using a more advanced queue // (such as a fibonaacci queue) with a fast decrease priority. - distance_to->resize (0); - distance_to->resize (objects_.length); - for (unsigned i = 0; i < objects_.length; i++) + for (unsigned i = 0; i < vertices_.length; i++) { - if (i == objects_.length - 1) - (*distance_to)[i] = 0; + if (i == vertices_.length - 1) + vertices_[i].distance = 0; else - (*distance_to)[i] = hb_int_max (int64_t); + vertices_[i].distance = hb_int_max (int64_t); } hb_priority_queue_t queue; - queue.insert (objects_.length - 1, 0); + queue.insert (vertices_.length - 1, 0); hb_set_t visited; @@ -396,45 +453,45 @@ struct graph_t { unsigned next_idx = queue.extract_minimum ().first; if (visited.has (next_idx)) continue; - const auto& next = objects_[next_idx]; - int64_t next_distance = (*distance_to)[next_idx]; + const auto& next = vertices_[next_idx]; + int64_t next_distance = vertices_[next_idx].distance; visited.add (next_idx); - for (const auto& link : next.links) + for (const auto& link : next.obj.links) { if (visited.has (link.objidx)) continue; - const auto& child = objects_[link.objidx]; + const auto& child = vertices_[link.objidx].obj; int64_t child_weight = child.tail - child.head + (!link.is_wide ? (1 << 16) : ((int64_t) 1 << 32)); int64_t child_distance = next_distance + child_weight; - if (child_distance < (*distance_to)[link.objidx]) + if (child_distance < vertices_[link.objidx].distance) { - (*distance_to)[link.objidx] = child_distance; + vertices_[link.objidx].distance = child_distance; queue.insert (link.objidx, child_distance); } } } // TODO(garretrieger): Handle this. If anything is left, part of the graph is disconnected. assert (queue.is_empty ()); + distance_invalid = false; } int64_t compute_offset ( unsigned parent_idx, - const hb_serialize_context_t::object_t::link_t& link, - const hb_vector_t& start_positions, - const hb_vector_t& end_positions) + const hb_serialize_context_t::object_t::link_t& link) const { - unsigned child_idx = link.objidx; + const auto& parent = vertices_[parent_idx]; + const auto& child = vertices_[link.objidx]; int64_t offset = 0; switch ((hb_serialize_context_t::whence_t) link.whence) { case hb_serialize_context_t::whence_t::Head: - offset = start_positions[child_idx] - start_positions[parent_idx]; break; + offset = child.start - parent.start; break; case hb_serialize_context_t::whence_t::Tail: - offset = start_positions[child_idx] - end_positions[parent_idx]; break; + offset = child.start - parent.end; break; case hb_serialize_context_t::whence_t::Absolute: - offset = start_positions[child_idx]; break; + offset = child.start; break; } assert (offset >= link.bias); @@ -443,7 +500,7 @@ struct graph_t } bool is_valid_offset (int64_t offset, - const hb_serialize_context_t::object_t::link_t& link) + const hb_serialize_context_t::object_t::link_t& link) const { if (link.is_signed) { @@ -465,13 +522,13 @@ struct graph_t * Updates all objidx's in all links using the provided mapping. */ void remap_obj_indices (const hb_vector_t& id_map, - hb_vector_t* sorted_graph) + hb_vector_t* sorted_graph) const { for (unsigned i = 0; i < sorted_graph->length; i++) { - for (unsigned j = 0; j < (*sorted_graph)[i].links.length; j++) + for (unsigned j = 0; j < (*sorted_graph)[i].obj.links.length; j++) { - auto& link = (*sorted_graph)[i].links[j]; + auto& link = (*sorted_graph)[i].obj.links[j]; link.objidx = id_map[link.objidx]; } } @@ -480,7 +537,7 @@ struct graph_t template void serialize_link_of_type (const hb_serialize_context_t::object_t::link_t& link, char* head, - hb_serialize_context_t* c) + hb_serialize_context_t* c) const { OT::Offset* offset = reinterpret_cast*> (head + link.position); *offset = 0; @@ -494,7 +551,7 @@ struct graph_t void serialize_link (const hb_serialize_context_t::object_t::link_t& link, char* head, - hb_serialize_context_t* c) + hb_serialize_context_t* c) const { if (link.is_wide) { @@ -515,8 +572,13 @@ struct graph_t } public: - hb_vector_t objects_; + // TODO(garretrieger): make private, will need to move most of offset overflow code into graph. + hb_vector_t vertices_; + private: hb_vector_t clone_buffers_; + bool edge_count_invalid; + bool distance_invalid; + bool positions_invalid; }; @@ -540,17 +602,12 @@ hb_resolve_overflows (const hb_vector_t& pac DEBUG_MSG (SUBSET_REPACK, nullptr, "Over flow resolution round %d", round); sorted_graph.print_overflows (overflows); - // TODO(garretrieger): cache ege count in the graph object . Will need to be invalidated - // by graph modifications. - hb_vector_t edge_count; - sorted_graph.incoming_edge_count (&edge_count); - // Try resolving the furthest overflow first. bool resolution_attempted = false; for (int i = overflows.length - 1; i >= 0; i--) { const graph_t::overflow_record_t& r = overflows[i]; - if (edge_count[r.link->objidx] > 1) + if (sorted_graph.vertices_[r.link->objidx].incoming_edges > 1) { DEBUG_MSG (SUBSET_REPACK, nullptr, "Duplicating %d => %d", r.parent, r.link->objidx); diff --git a/src/test-repacker.cc b/src/test-repacker.cc index e18cabdab..0b237de2d 100644 --- a/src/test-repacker.cc +++ b/src/test-repacker.cc @@ -197,20 +197,20 @@ static void test_sort_kahn_1 () graph_t graph (c.object_graph ()); graph.sort_kahn (); - assert(strncmp (graph.objects_[3].head, "abc", 3) == 0); - assert(graph.objects_[3].links.length == 2); - assert(graph.objects_[3].links[0].objidx == 2); - assert(graph.objects_[3].links[1].objidx == 1); + assert(strncmp (graph.object (3).head, "abc", 3) == 0); + assert(graph.object (3).links.length == 2); + assert(graph.object (3).links[0].objidx == 2); + assert(graph.object (3).links[1].objidx == 1); - assert(strncmp (graph.objects_[2].head, "def", 3) == 0); - assert(graph.objects_[2].links.length == 1); - assert(graph.objects_[2].links[0].objidx == 0); + assert(strncmp (graph.object (2).head, "def", 3) == 0); + assert(graph.object (2).links.length == 1); + assert(graph.object (2).links[0].objidx == 0); - assert(strncmp (graph.objects_[1].head, "jkl", 3) == 0); - assert(graph.objects_[1].links.length == 0); + assert(strncmp (graph.object (1).head, "jkl", 3) == 0); + assert(graph.object (1).links.length == 0); - assert(strncmp (graph.objects_[0].head, "ghi", 3) == 0); - assert(graph.objects_[0].links.length == 0); + assert(strncmp (graph.object (0).head, "ghi", 3) == 0); + assert(graph.object (0).links.length == 0); } static void test_sort_kahn_2 () @@ -224,25 +224,25 @@ static void test_sort_kahn_2 () graph.sort_kahn (); - assert(strncmp (graph.objects_[4].head, "abc", 3) == 0); - assert(graph.objects_[4].links.length == 3); - assert(graph.objects_[4].links[0].objidx == 3); - assert(graph.objects_[4].links[1].objidx == 0); - assert(graph.objects_[4].links[2].objidx == 2); + assert(strncmp (graph.object (4).head, "abc", 3) == 0); + assert(graph.object (4).links.length == 3); + assert(graph.object (4).links[0].objidx == 3); + assert(graph.object (4).links[1].objidx == 0); + assert(graph.object (4).links[2].objidx == 2); - assert(strncmp (graph.objects_[3].head, "def", 3) == 0); - assert(graph.objects_[3].links.length == 1); - assert(graph.objects_[3].links[0].objidx == 1); + assert(strncmp (graph.object (3).head, "def", 3) == 0); + assert(graph.object (3).links.length == 1); + assert(graph.object (3).links[0].objidx == 1); - assert(strncmp (graph.objects_[2].head, "mn", 2) == 0); - assert(graph.objects_[2].links.length == 0); + assert(strncmp (graph.object (2).head, "mn", 2) == 0); + assert(graph.object (2).links.length == 0); - assert(strncmp (graph.objects_[1].head, "ghi", 3) == 0); - assert(graph.objects_[1].links.length == 1); - assert(graph.objects_[1].links[0].objidx == 0); + assert(strncmp (graph.object (1).head, "ghi", 3) == 0); + assert(graph.object (1).links.length == 1); + assert(graph.object (1).links[0].objidx == 0); - assert(strncmp (graph.objects_[0].head, "jkl", 3) == 0); - assert(graph.objects_[0].links.length == 0); + assert(strncmp (graph.object (0).head, "jkl", 3) == 0); + assert(graph.object (0).links.length == 0); } static void test_sort_shortest () @@ -256,25 +256,25 @@ static void test_sort_shortest () graph.sort_shortest_distance (); - assert(strncmp (graph.objects_[4].head, "abc", 3) == 0); - assert(graph.objects_[4].links.length == 3); - assert(graph.objects_[4].links[0].objidx == 2); - assert(graph.objects_[4].links[1].objidx == 0); - assert(graph.objects_[4].links[2].objidx == 3); + assert(strncmp (graph.object (4).head, "abc", 3) == 0); + assert(graph.object (4).links.length == 3); + assert(graph.object (4).links[0].objidx == 2); + assert(graph.object (4).links[1].objidx == 0); + assert(graph.object (4).links[2].objidx == 3); - assert(strncmp (graph.objects_[3].head, "mn", 2) == 0); - assert(graph.objects_[3].links.length == 0); + assert(strncmp (graph.object (3).head, "mn", 2) == 0); + assert(graph.object (3).links.length == 0); - assert(strncmp (graph.objects_[2].head, "def", 3) == 0); - assert(graph.objects_[2].links.length == 1); - assert(graph.objects_[2].links[0].objidx == 1); + assert(strncmp (graph.object (2).head, "def", 3) == 0); + assert(graph.object (2).links.length == 1); + assert(graph.object (2).links[0].objidx == 1); - assert(strncmp (graph.objects_[1].head, "ghi", 3) == 0); - assert(graph.objects_[1].links.length == 1); - assert(graph.objects_[1].links[0].objidx == 0); + assert(strncmp (graph.object (1).head, "ghi", 3) == 0); + assert(graph.object (1).links.length == 1); + assert(graph.object (1).links[0].objidx == 0); - assert(strncmp (graph.objects_[0].head, "jkl", 3) == 0); - assert(graph.objects_[0].links.length == 0); + assert(strncmp (graph.object (0).head, "jkl", 3) == 0); + assert(graph.object (0).links.length == 0); } static void test_duplicate_leaf () @@ -287,28 +287,28 @@ static void test_duplicate_leaf () graph_t graph (c.object_graph ()); graph.duplicate (4, 1); - assert(strncmp (graph.objects_[5].head, "abc", 3) == 0); - assert(graph.objects_[5].links.length == 3); - assert(graph.objects_[5].links[0].objidx == 3); - assert(graph.objects_[5].links[1].objidx == 4); - assert(graph.objects_[5].links[2].objidx == 0); + assert(strncmp (graph.object (5).head, "abc", 3) == 0); + assert(graph.object (5).links.length == 3); + assert(graph.object (5).links[0].objidx == 3); + assert(graph.object (5).links[1].objidx == 4); + assert(graph.object (5).links[2].objidx == 0); - assert(strncmp (graph.objects_[4].head, "jkl", 3) == 0); - assert(graph.objects_[4].links.length == 0); + assert(strncmp (graph.object (4).head, "jkl", 3) == 0); + assert(graph.object (4).links.length == 0); - assert(strncmp (graph.objects_[3].head, "def", 3) == 0); - assert(graph.objects_[3].links.length == 1); - assert(graph.objects_[3].links[0].objidx == 2); + assert(strncmp (graph.object (3).head, "def", 3) == 0); + assert(graph.object (3).links.length == 1); + assert(graph.object (3).links[0].objidx == 2); - assert(strncmp (graph.objects_[2].head, "ghi", 3) == 0); - assert(graph.objects_[2].links.length == 1); - assert(graph.objects_[2].links[0].objidx == 1); + assert(strncmp (graph.object (2).head, "ghi", 3) == 0); + assert(graph.object (2).links.length == 1); + assert(graph.object (2).links[0].objidx == 1); - assert(strncmp (graph.objects_[1].head, "jkl", 3) == 0); - assert(graph.objects_[1].links.length == 0); + assert(strncmp (graph.object (1).head, "jkl", 3) == 0); + assert(graph.object (1).links.length == 0); - assert(strncmp (graph.objects_[0].head, "mn", 2) == 0); - assert(graph.objects_[0].links.length == 0); + assert(strncmp (graph.object (0).head, "mn", 2) == 0); + assert(graph.object (0).links.length == 0); } static void test_duplicate_interior () @@ -321,33 +321,33 @@ static void test_duplicate_interior () graph_t graph (c.object_graph ()); graph.duplicate (3, 2); - assert(strncmp (graph.objects_[6].head, "abc", 3) == 0); - assert(graph.objects_[6].links.length == 3); - assert(graph.objects_[6].links[0].objidx == 4); - assert(graph.objects_[6].links[1].objidx == 2); - assert(graph.objects_[6].links[2].objidx == 1); + assert(strncmp (graph.object (6).head, "abc", 3) == 0); + assert(graph.object (6).links.length == 3); + assert(graph.object (6).links[0].objidx == 4); + assert(graph.object (6).links[1].objidx == 2); + assert(graph.object (6).links[2].objidx == 1); - assert(strncmp (graph.objects_[5].head, "jkl", 3) == 0); - assert(graph.objects_[5].links.length == 1); - assert(graph.objects_[5].links[0].objidx == 0); + assert(strncmp (graph.object (5).head, "jkl", 3) == 0); + assert(graph.object (5).links.length == 1); + assert(graph.object (5).links[0].objidx == 0); - assert(strncmp (graph.objects_[4].head, "def", 3) == 0); - assert(graph.objects_[4].links.length == 1); - assert(graph.objects_[4].links[0].objidx == 3); + assert(strncmp (graph.object (4).head, "def", 3) == 0); + assert(graph.object (4).links.length == 1); + assert(graph.object (4).links[0].objidx == 3); - assert(strncmp (graph.objects_[3].head, "ghi", 3) == 0); - assert(graph.objects_[3].links.length == 1); - assert(graph.objects_[3].links[0].objidx == 5); + assert(strncmp (graph.object (3).head, "ghi", 3) == 0); + assert(graph.object (3).links.length == 1); + assert(graph.object (3).links[0].objidx == 5); - assert(strncmp (graph.objects_[2].head, "jkl", 3) == 0); - assert(graph.objects_[2].links.length == 1); - assert(graph.objects_[2].links[0].objidx == 0); + assert(strncmp (graph.object (2).head, "jkl", 3) == 0); + assert(graph.object (2).links.length == 1); + assert(graph.object (2).links[0].objidx == 0); - assert(strncmp (graph.objects_[1].head, "mn", 2) == 0); - assert(graph.objects_[1].links.length == 0); + assert(strncmp (graph.object (1).head, "mn", 2) == 0); + assert(graph.object (1).links.length == 0); - assert(strncmp (graph.objects_[0].head, "opqrst", 6) == 0); - assert(graph.objects_[0].links.length == 0); + assert(strncmp (graph.object (0).head, "opqrst", 6) == 0); + assert(graph.object (0).links.length == 0); } static void From a7a86a6eb4da25822cf0d42d7dd1668a15325a8f Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Fri, 6 Nov 2020 16:22:48 -0800 Subject: [PATCH 16/30] [subset] Add prioritization offset resolution. Vertices can now be prioritized to force them to sort closer to their parent. The resolver will attempt to use this for overflows on non-shared vertices. --- src/hb-repacker.hh | 107 +++++++++++++++++++++++++++++++++++-------- src/test-repacker.cc | 1 + 2 files changed, 88 insertions(+), 20 deletions(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index d6f05c1f1..b2f48762d 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -41,6 +41,13 @@ struct graph_t struct vertex_t { + vertex_t () : + distance (0), + incoming_edges (0), + start (0), + end (0), + priority(0) {} + void fini () { obj.fini (); } hb_serialize_context_t::object_t obj; @@ -48,6 +55,38 @@ struct graph_t unsigned incoming_edges; unsigned start; unsigned end; + unsigned priority; + + bool is_shared () const + { + return incoming_edges > 1; + } + + bool is_leaf () const + { + return !obj.links.length; + } + + void raise_priority () + { + priority++; + } + + int64_t modified_distance (unsigned order) const + { + // TODO(garretrieger): once priority is high enough, should try + // setting distance = 0 which will force to sort immediately after + // it's parent where possible. + int64_t modified_distance = distance + distance_modifier (); + return (modified_distance << 24) | (0x00FFFFFF & order); + } + + int64_t distance_modifier () const + { + if (!priority) return 0; + int64_t table_size = obj.tail - obj.head; + return -(table_size - table_size / (1 << priority)); + } }; struct overflow_record_t @@ -246,7 +285,7 @@ struct graph_t // Object graphs are in reverse order, the first object is at the end // of the vector. Since the graph is topologically sorted it's safe to // assume the first object has no incoming edges. - queue.insert (root_idx (), add_order(root ().distance, 0)); + queue.insert (root_idx (), root ().modified_distance (0)); int new_id = root_idx (); unsigned order = 1; while (!queue.is_empty ()) @@ -265,7 +304,8 @@ struct graph_t // way. More specifically this is set up so that if a set of objects have the same // distance they'll be added to the topolical order in the order that they are // referenced from the parent object. - queue.insert (link.objidx, add_order(vertices_[link.objidx].distance, order++)); + queue.insert (link.objidx, + vertices_[link.objidx].modified_distance (order++)); } } @@ -290,6 +330,9 @@ struct graph_t */ void duplicate (unsigned parent_idx, unsigned child_idx) { + DEBUG_MSG (SUBSET_REPACK, nullptr, " Duplicating %d => %d", + parent_idx, child_idx); + positions_invalid = true; auto& child = vertices_[child_idx]; @@ -325,10 +368,25 @@ struct graph_t vertices_[vertices_.length - 1] = root; } + /* + * Raises the sorting priority of all children. + */ + void raise_childrens_priority (unsigned parent_idx) + { + DEBUG_MSG (SUBSET_REPACK, nullptr, " Raising priority of all children of %d", + parent_idx); + // This operation doesn't change ordering until a sort is run, so no need + // to invalidate positions. It does not change graph structure so no need + // to update distances or edge counts. + auto& parent = vertices_[parent_idx].obj; + for (unsigned i = 0; i < parent.links.length; i++) + vertices_[parent.links[i].objidx].raise_priority (); + } + /* * Will any offsets overflow on graph when it's serialized? */ - bool will_overflow (hb_vector_t* overflows) + bool will_overflow (hb_vector_t* overflows = nullptr) { if (overflows) overflows->resize (0); update_positions (); @@ -362,7 +420,7 @@ struct graph_t for (const auto& o : overflows) { const auto& child = vertices_[o.link->objidx]; - DEBUG_MSG (SUBSET_REPACK, nullptr, "overflow from %d => %d (%d incoming , %d outgoing)", + DEBUG_MSG (SUBSET_REPACK, nullptr, " overflow from %d => %d (%d incoming , %d outgoing)", o.parent, o.link->objidx, child.incoming_edges, @@ -393,12 +451,6 @@ struct graph_t edge_count_invalid = false; } - - int64_t add_order (int64_t distance, unsigned order) - { - return (distance << 24) | (0x00FFFFFF & order); - } - /* * compute the serialized start and end positions for each vertex. */ @@ -589,9 +641,11 @@ struct graph_t inline void hb_resolve_overflows (const hb_vector_t& packed, hb_serialize_context_t* c) { + // Kahn sort is ~twice as fast as shortest distance sort and works for many fonts + // so try it first. graph_t sorted_graph (packed); sorted_graph.sort_kahn (); - if (!sorted_graph.will_overflow (nullptr)) return; + if (!sorted_graph.will_overflow ()) return; sorted_graph.sort_shortest_distance (); @@ -599,36 +653,49 @@ hb_resolve_overflows (const hb_vector_t& pac hb_vector_t overflows; // TODO(garretrieger): select a good limit for max rounds. while (sorted_graph.will_overflow (&overflows) && round++ < 10) { - DEBUG_MSG (SUBSET_REPACK, nullptr, "Over flow resolution round %d", round); + DEBUG_MSG (SUBSET_REPACK, nullptr, "=== Over flow resolution round %d ===", round); sorted_graph.print_overflows (overflows); - // Try resolving the furthest overflow first. bool resolution_attempted = false; + hb_set_t priority_bumped_parents; + // Try resolving the furthest overflows first. for (int i = overflows.length - 1; i >= 0; i--) { const graph_t::overflow_record_t& r = overflows[i]; - if (sorted_graph.vertices_[r.link->objidx].incoming_edges > 1) + const auto& child = sorted_graph.vertices_[r.link->objidx]; + if (child.is_shared ()) { - DEBUG_MSG (SUBSET_REPACK, nullptr, "Duplicating %d => %d", - r.parent, r.link->objidx); // The child object is shared, we may be able to eliminate the overflow // by duplicating it. sorted_graph.duplicate (r.parent, r.link->objidx); - sorted_graph.sort_shortest_distance (); resolution_attempted = true; break; } + if (child.is_leaf () && !priority_bumped_parents.has (r.parent)) + { + // TODO(garretrieger): initially limiting this to leaf's but likely + // can be used for non-leafs as well. + // TODO(garretrieger): add a maximum priority, don't try to raise past this. + sorted_graph.raise_childrens_priority (r.parent); + priority_bumped_parents.add (r.parent); + resolution_attempted = true; + continue; + } + // TODO(garretrieger): add additional offset resolution strategies // - Promotion to extension lookups. // - Table splitting. } - if (!resolution_attempted) + if (resolution_attempted) { - DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :("); - break; + sorted_graph.sort_shortest_distance (); + continue; } + + DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :("); + break; } sorted_graph.serialize (c); diff --git a/src/test-repacker.cc b/src/test-repacker.cc index 0b237de2d..33067b252 100644 --- a/src/test-repacker.cc +++ b/src/test-repacker.cc @@ -449,6 +449,7 @@ static void test_resolve_overflows_via_duplication () // TODO(garretrieger): update will_overflow tests to check the overflows array. // TODO(garretrieger): add a test(s) using a real font. +// TODO(garretrieger): add tests for priority raising. int main (int argc, char **argv) From 6e9468fcfb71c731b0ec5a5e9c434119f16245e9 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Mon, 9 Nov 2020 16:52:36 -0800 Subject: [PATCH 17/30] [subset] cleanup memory leaks in the repacker. --- src/hb-repacker.hh | 7 ++++++- src/hb-subset.cc | 12 ++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index b2f48762d..2ffc69c65 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -77,6 +77,7 @@ struct graph_t // TODO(garretrieger): once priority is high enough, should try // setting distance = 0 which will force to sort immediately after // it's parent where possible. + int64_t modified_distance = distance + distance_modifier (); return (modified_distance << 24) | (0x00FFFFFF & order); } @@ -254,6 +255,8 @@ struct graph_t remap_obj_indices (id_map, &sorted_graph); sorted_graph.as_array ().reverse (); + + vertices_.fini_deep (); vertices_ = sorted_graph; sorted_graph.fini_deep (); } @@ -319,6 +322,8 @@ struct graph_t remap_obj_indices (id_map, &sorted_graph); sorted_graph.as_array ().reverse (); + + vertices_.fini_deep (); vertices_ = sorted_graph; sorted_graph.fini_deep (); } @@ -335,11 +340,11 @@ struct graph_t positions_invalid = true; + auto* clone = vertices_.push (); auto& child = vertices_[child_idx]; clone_buffer_t* buffer = clone_buffers_.push (); buffer->copy (child.obj); - auto* clone = vertices_.push (); clone->obj.head = buffer->head; clone->obj.tail = buffer->tail; clone->distance = child.distance; diff --git a/src/hb-subset.cc b/src/hb-subset.cc index 2db027034..46cfbe9d0 100644 --- a/src/hb-subset.cc +++ b/src/hb-subset.cc @@ -142,10 +142,14 @@ _subset (hb_subset_plan_t *plan) if (needed) { hb_blob_t *dest_blob = _repack (tag, serializer); - if (!dest_blob) return false; - DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c final subset table size: %u bytes.", HB_UNTAG (tag), dest_blob->length); - result = c.plan->add_table (tag, dest_blob); - hb_blob_destroy (dest_blob); + if (dest_blob) + { + DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c final subset table size: %u bytes.", HB_UNTAG (tag), dest_blob->length); + result = c.plan->add_table (tag, dest_blob); + hb_blob_destroy (dest_blob); + } else { + result = false; + } } else { From b8b8c58b9ff9b51108caf47f5d98a15801b39058 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Tue, 10 Nov 2020 11:56:09 -0800 Subject: [PATCH 18/30] [subset] add tests that check for successful repacking of a real font file. --- configure.ac | 1 + test/subset/Makefile.am | 2 + test/subset/data/Makefile.am | 2 +- .../data/fonts/NotoNastaliqUrdu-Bold.ttf | Bin 0 -> 459600 bytes test/subset/data/repack_tests/Makefile.am | 22 ++++ .../subset/data/repack_tests/Makefile.sources | 12 ++ .../advanced_prioritization.tests | 72 ++++++++++++ test/subset/data/repack_tests/basic.tests | 52 +++++++++ .../data/repack_tests/prioritization.tests | 77 +++++++++++++ .../data/repack_tests/table_duplication.tests | 97 ++++++++++++++++ test/subset/meson.build | 22 ++++ test/subset/repack_test.py | 36 ++++++ test/subset/run-repack-tests.py | 104 ++++++++++++++++++ 13 files changed, 498 insertions(+), 1 deletion(-) create mode 100644 test/subset/data/fonts/NotoNastaliqUrdu-Bold.ttf create mode 100644 test/subset/data/repack_tests/Makefile.am create mode 100644 test/subset/data/repack_tests/Makefile.sources create mode 100644 test/subset/data/repack_tests/advanced_prioritization.tests create mode 100644 test/subset/data/repack_tests/basic.tests create mode 100644 test/subset/data/repack_tests/prioritization.tests create mode 100644 test/subset/data/repack_tests/table_duplication.tests create mode 100644 test/subset/repack_test.py create mode 100755 test/subset/run-repack-tests.py diff --git a/configure.ac b/configure.ac index d7f1e2673..db596bc4c 100644 --- a/configure.ac +++ b/configure.ac @@ -432,6 +432,7 @@ test/shaping/data/in-house/Makefile test/shaping/data/text-rendering-tests/Makefile test/subset/Makefile test/subset/data/Makefile +test/subset/data/repack_tests/Makefile docs/Makefile docs/version.xml ]) diff --git a/test/subset/Makefile.am b/test/subset/Makefile.am index 47b003932..cfd739b31 100644 --- a/test/subset/Makefile.am +++ b/test/subset/Makefile.am @@ -13,7 +13,9 @@ libs: EXTRA_DIST += \ meson.build \ run-tests.py \ + run-repack-tests.py \ subset_test_suite.py \ + repack_test.py \ $(NULL) CLEANFILES += \ diff --git a/test/subset/data/Makefile.am b/test/subset/data/Makefile.am index daebed90b..73585f85c 100644 --- a/test/subset/data/Makefile.am +++ b/test/subset/data/Makefile.am @@ -3,7 +3,7 @@ NULL = EXTRA_DIST = CLEANFILES = -SUBDIRS = +SUBDIRS = repack_tests EXTRA_DIST += \ $(TESTS) \ diff --git a/test/subset/data/fonts/NotoNastaliqUrdu-Bold.ttf b/test/subset/data/fonts/NotoNastaliqUrdu-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d05dabe0b5312b09de1ac70b867019b1fb8116fc GIT binary patch literal 459600 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%0Dg}H@LCETc-y&W^2Z(@zqt|IqclfHY>AK_)C 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 literal 0 HcmV?d00001 diff --git a/test/subset/data/repack_tests/Makefile.am b/test/subset/data/repack_tests/Makefile.am new file mode 100644 index 000000000..8e814fb2c --- /dev/null +++ b/test/subset/data/repack_tests/Makefile.am @@ -0,0 +1,22 @@ +# Process this file with automake to produce Makefile.in + +NULL = +EXTRA_DIST = +CLEANFILES = +SUBDIRS = + +EXTRA_DIST += + $(TESTS) \ + $(NULL) + +# Convenience targets: +lib: libs # Always build subsetter lib in this subdir +libs: + @$(MAKE) $(AM_MAKEFLAGS) -C $(top_builddir)/src libs + +TEST_EXTENSIONS = .tests +TESTS_LOG_COMPILER = $(srcdir)/../../run-repack-tests.py $(top_builddir)/util/hb-subset$(EXEEXT) +include Makefile.sources + + +-include $(top_srcdir)/git.mk diff --git a/test/subset/data/repack_tests/Makefile.sources b/test/subset/data/repack_tests/Makefile.sources new file mode 100644 index 000000000..e778e52a7 --- /dev/null +++ b/test/subset/data/repack_tests/Makefile.sources @@ -0,0 +1,12 @@ +TESTS = \ + basic.tests \ + prioritization.tests \ + table_duplication.tests \ + $(NULL) + +XFAIL_TESTS = \ + advanced_prioritization.tests \ + $(NULL) + +DISABLED_TESTS = \ + $(NULL) diff --git a/test/subset/data/repack_tests/advanced_prioritization.tests b/test/subset/data/repack_tests/advanced_prioritization.tests new file mode 100644 index 000000000..adcbb001b --- /dev/null +++ b/test/subset/data/repack_tests/advanced_prioritization.tests @@ -0,0 +1,72 @@ +NotoNastaliqUrdu-Bold.ttf +0x0020 +0x0028 +0x0029 +0x002C +0x002D +0x002E +0x0030 +0x0031 +0x0032 +0x0033 +0x0034 +0x0035 +0x0036 +0x0037 +0x0038 +0x0039 +0x003A +0x060C +0x061F +0x0621 +0x0622 +0x0623 +0x0624 +0x0625 +0x0626 +0x0627 +0x0628 +0x0629 +0x062A +0x062B +0x062C +0x062D +0x062E +0x062F +0x0630 +0x0631 +0x0632 +0x0633 +0x0634 +0x0635 +0x0636 +0x0637 +0x0638 +0x0639 +0x063A +0x0640 +0x0641 +0x0642 +0x0643 +0x0644 +0x0645 +0x0646 +0x0647 +0x0648 +0x0649 +0x064A +0x064B +0x064C +0x064F +0x0651 +0x067E +0x0686 +0x0698 +0x06A9 +0x06AF +0x06BE +0x06CC +0x200C +0x200D +0x200E + diff --git a/test/subset/data/repack_tests/basic.tests b/test/subset/data/repack_tests/basic.tests new file mode 100644 index 000000000..896cc9b48 --- /dev/null +++ b/test/subset/data/repack_tests/basic.tests @@ -0,0 +1,52 @@ +NotoNastaliqUrdu-Bold.ttf +0x060C +0x061F +0x0621 +0x0622 +0x0623 +0x0624 +0x0625 +0x0626 +0x0627 +0x0628 +0x0629 +0x062A +0x062B +0x062C +0x062D +0x062E +0x062F +0x0630 +0x0631 +0x0632 +0x0633 +0x0634 +0x0635 +0x0636 +0x0637 +0x0638 +0x0639 +0x063A +0x0640 +0x0641 +0x0642 +0x0643 +0x0644 +0x0645 +0x0646 +0x0647 +0x0648 +0x0649 +0x064A +0x064B +0x064F +0x0651 +0x067E +0x0686 +0x0698 +0x06A9 +0x06AF +0x06CC +0x200C +0x200D +0x200E diff --git a/test/subset/data/repack_tests/prioritization.tests b/test/subset/data/repack_tests/prioritization.tests new file mode 100644 index 000000000..63b437c92 --- /dev/null +++ b/test/subset/data/repack_tests/prioritization.tests @@ -0,0 +1,77 @@ +NotoNastaliqUrdu-Bold.ttf +0x0020 +0x0028 +0x0029 +0x002C +0x002D +0x002E +0x0030 +0x0031 +0x0032 +0x0033 +0x0034 +0x0035 +0x0036 +0x0037 +0x0038 +0x0039 +0x003A +0x060C +0x061F +0x0621 +0x0622 +0x0623 +0x0624 +0x0625 +0x0626 +0x0627 +0x0628 +0x0629 +0x062A +0x062B +0x062C +0x062D +0x062E +0x062F +0x0630 +0x0631 +0x0632 +0x0633 +0x0634 +0x0635 +0x0636 +0x0637 +0x0638 +0x0639 +0x063A +0x0640 +0x0641 +0x0642 +0x0643 +0x0644 +0x0645 +0x0646 +0x0647 +0x0648 +0x0649 +0x064A +0x064B +0x064F +0x0651 +0x0653 +0x0679 +0x067E +0x0686 +0x0688 +0x0691 +0x0698 +0x06A9 +0x06AF +0x06BA +0x06BE +0x06C1 +0x06CC +0x06D2 +0x200C +0x200D +0x200E diff --git a/test/subset/data/repack_tests/table_duplication.tests b/test/subset/data/repack_tests/table_duplication.tests new file mode 100644 index 000000000..3cc90d6bc --- /dev/null +++ b/test/subset/data/repack_tests/table_duplication.tests @@ -0,0 +1,97 @@ +NotoNastaliqUrdu-Bold.ttf +0x0028 +0x0029 +0x002C +0x002D +0x002E +0x0030 +0x0031 +0x0032 +0x0033 +0x0034 +0x0035 +0x0036 +0x0037 +0x0038 +0x0039 +0x003A +0x0041 +0x0042 +0x0043 +0x0044 +0x0045 +0x0046 +0x0047 +0x0048 +0x0049 +0x004C +0x004D +0x004E +0x004F +0x0050 +0x0052 +0x0053 +0x0054 +0x0055 +0x0056 +0x0057 +0x0061 +0x0062 +0x0063 +0x0064 +0x0065 +0x0066 +0x0067 +0x0068 +0x0069 +0x006B +0x006C +0x006D +0x006E +0x006F +0x0070 +0x0072 +0x0073 +0x0074 +0x0075 +0x0076 +0x0077 +0x0078 +0x0079 +0x060C +0x0626 +0x0627 +0x0628 +0x062A +0x062C +0x062D +0x062E +0x062F +0x0631 +0x0632 +0x0633 +0x0634 +0x0635 +0x0636 +0x0637 +0x0638 +0x0639 +0x0641 +0x0642 +0x0644 +0x0645 +0x0646 +0x0648 +0x0653 +0x0679 +0x067E +0x0686 +0x0688 +0x0691 +0x06A9 +0x06AF +0x06BA +0x06BE +0x06C1 +0x06CC +0x06D2 diff --git a/test/subset/meson.build b/test/subset/meson.build index 458bf9646..5da61a9e0 100644 --- a/test/subset/meson.build +++ b/test/subset/meson.build @@ -28,6 +28,13 @@ tests = [ 'cbdt', ] +repack_tests = [ + 'basic', + 'prioritization', + 'table_duplication', +] + + run_test = find_program('run-tests.py') foreach t : tests @@ -45,3 +52,18 @@ foreach t : tests suite: ['subset', 'slow'], ) endforeach + +run_repack_test = find_program('run-repack-tests.py') + +foreach t : repack_tests + fname = '@0@.tests'.format(t) + + test(t, run_repack_test, + args: [ + hb_subset, + join_paths(meson.current_source_dir(), 'data', 'repack_tests', fname), + ], + workdir: join_paths(meson.current_build_dir(), '..', '..'), + suite: ['subset', 'repack'], + ) +endforeach diff --git a/test/subset/repack_test.py b/test/subset/repack_test.py new file mode 100644 index 000000000..2b53dd333 --- /dev/null +++ b/test/subset/repack_test.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +import os + +# Parses a single repacking test file. The first line of the file is +# the name of the font to use and the remaining lines define the set of +# codepoints in the subset. +class RepackTest: + + def __init__(self, test_path, definition): + self.test_path = test_path + self.font_name = None + self.codepoints = set () + self._parse(definition) + + def font_path(self): + return os.path.join (self._base_path (), "fonts", self.font_name) + + def codepoints_string (self): + return ",".join (self.codepoints) + + def _base_path(self): + return os.path.join( + os.path.dirname(self.test_path), + "../") + + + def _parse(self, definition): + lines = definition.splitlines () + self.font_name = lines.pop (0) + for line in lines: + line = line.strip() + if not line: + continue + + self.codepoints.add (line) diff --git a/test/subset/run-repack-tests.py b/test/subset/run-repack-tests.py new file mode 100755 index 000000000..22154ba9f --- /dev/null +++ b/test/subset/run-repack-tests.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 + +# Runs a subsetting test suite. Compares the results of subsetting via harfbuzz +# to subsetting via fonttools. + +from difflib import unified_diff +import os +import re +import subprocess +import sys +import tempfile +import shutil +import io + +from repack_test import RepackTest + +try: + from fontTools.ttLib import TTFont +except ImportError: + print ("fonttools is not present, skipping test.") + sys.exit (77) + +ots_sanitize = shutil.which ("ots-sanitize") + +def cmd (command): + p = subprocess.Popen ( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True) + (stdoutdata, stderrdata) = p.communicate () + print (stderrdata, end="", file=sys.stderr) + return stdoutdata, p.returncode + +def fail_test (test, cli_args, message): + print ('ERROR: %s' % message) + print ('Test State:') + print (' test.font_name %s' % test.font_name) + print (' test.test_path %s' % os.path.abspath (test.test_path)) + return 1 + +def run_test (test, should_check_ots): + out_file = os.path.join (tempfile.mkdtemp (), test.font_name + '-subset.ttf') + cli_args = [hb_subset, + "--font-file=" + test.font_path (), + "--output-file=" + out_file, + "--unicodes=%s" % test.codepoints_string (), + "--drop-tables-=GPOS,GSUB,GDEF",] + print (' '.join (cli_args)) + _, return_code = cmd (cli_args) + + if return_code: + return fail_test (test, cli_args, "%s returned %d" % (' '.join (cli_args), return_code)) + + try: + with TTFont (out_file) as font: + pass + except Exception as e: + print (e) + return fail_test (test, cli_args, "ttx failed to parse the result") + + if should_check_ots: + print ("Checking output with ots-sanitize.") + if not check_ots (out_file): + return fail_test (test, cli_args, 'ots for subsetted file fails.') + + return 0 + +def has_ots (): + if not ots_sanitize: + print ("OTS is not present, skipping all ots checks.") + return False + return True + +def check_ots (path): + ots_report, returncode = cmd ([ots_sanitize, path]) + if returncode: + print ("OTS Failure: %s" % ots_report) + return False + return True + +args = sys.argv[1:] +if not args or sys.argv[1].find ('hb-subset') == -1 or not os.path.exists (sys.argv[1]): + sys.exit ("First argument does not seem to point to usable hb-subset.") +hb_subset, args = args[0], args[1:] + +if len (args) != 1: + sys.exit ("No tests supplied.") + +has_ots = has_ots() + +fails = 0 + +path = args[0] +if not path.endswith(".tests"): + sys.exit ("Not a valid test case path.") + +with open (path, mode="r", encoding="utf-8") as f: + # TODO(garretrieger): re-enable OTS checking. + fails += run_test (RepackTest (path, f.read ()), False) + + +if fails != 0: + sys.exit ("%d test(s) failed." % fails) +else: + print ("All tests passed.") From bb5c80a7c2d2454bba745a155146e7eaad912474 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Tue, 10 Nov 2020 14:11:57 -0800 Subject: [PATCH 19/30] [subset] add error tracking to the repacker. Also check for allocation failures as needed. --- src/hb-repacker.hh | 93 ++++++++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 31 deletions(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index 2ffc69c65..75ba39132 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -36,9 +36,6 @@ struct graph_t { - // TODO(garretrieger): add an error tracking system similar to what serialize_context_t - // does. - struct vertex_t { vertex_t () : @@ -100,13 +97,16 @@ struct graph_t { clone_buffer_t () : head (nullptr), tail (nullptr) {} - void copy (const hb_serialize_context_t::object_t& object) + bool copy (const hb_serialize_context_t::object_t& object) { fini (); unsigned size = object.tail - object.head; head = (char*) malloc (size); + if (!head) return false; + memcpy (head, object.head, size); tail = head + size; + return true; } char* head; @@ -130,7 +130,8 @@ struct graph_t graph_t (const hb_vector_t& objects) : edge_count_invalid (true), distance_invalid (true), - positions_invalid (true) + positions_invalid (true), + successful (true) { bool removed_nil = false; for (unsigned i = 0; i < objects.length; i++) @@ -160,6 +161,11 @@ struct graph_t clone_buffers_.fini_deep (); } + bool in_error () const + { + return !successful || vertices_.in_error () || clone_buffers_.in_error (); + } + const vertex_t& root () const { return vertices_[root_idx ()]; @@ -219,38 +225,35 @@ struct graph_t hb_vector_t queue; hb_vector_t sorted_graph; hb_vector_t id_map; - id_map.resize (vertices_.length); + check_success (id_map.resize (vertices_.length)); hb_vector_t removed_edges; - removed_edges.resize (vertices_.length); + check_success (removed_edges.resize (vertices_.length)); update_incoming_edge_count (); queue.push (root_idx ()); int new_id = vertices_.length - 1; - while (queue.length) + while (!queue.in_error () && queue.length) { unsigned next_id = queue[0]; - queue.remove(0); + queue.remove (0); vertex_t& next = vertices_[next_id]; sorted_graph.push (next); id_map[next_id] = new_id--; for (const auto& link : next.obj.links) { - // TODO(garretrieger): sort children from smallest to largest removed_edges[link.objidx]++; if (!(vertices_[link.objidx].incoming_edges - removed_edges[link.objidx])) queue.push (link.objidx); } } - if (new_id != -1) - { - // Graph is not fully connected, there are unsorted objects. - // TODO(garretrieger): handle this. - assert (false); - } + check_success (!queue.in_error ()); + check_success (!sorted_graph.in_error ()); + if (!check_success (new_id == -1)) + DEBUG_MSG (SUBSET_REPACK, nullptr, "Graph is not fully connected."); remap_obj_indices (id_map, &sorted_graph); @@ -279,10 +282,10 @@ struct graph_t hb_priority_queue_t queue; hb_vector_t sorted_graph; hb_vector_t id_map; - id_map.resize (vertices_.length); + check_success (id_map.resize (vertices_.length)); hb_vector_t removed_edges; - removed_edges.resize (vertices_.length); + check_success (removed_edges.resize (vertices_.length)); update_incoming_edge_count (); // Object graphs are in reverse order, the first object is at the end @@ -291,7 +294,7 @@ struct graph_t queue.insert (root_idx (), root ().modified_distance (0)); int new_id = root_idx (); unsigned order = 1; - while (!queue.is_empty ()) + while (!queue.in_error () && !queue.is_empty ()) { unsigned next_id = queue.extract_minimum().first; @@ -312,12 +315,10 @@ struct graph_t } } - if (new_id != -1) - { - // Graph is not fully connected, there are unsorted objects. - // TODO(garretrieger): handle this. - assert (false); - } + check_success (!queue.in_error ()); + check_success (!sorted_graph.in_error ()); + if (!check_success (new_id == -1)) + DEBUG_MSG (SUBSET_REPACK, nullptr, "Graph is not fully connected."); remap_obj_indices (id_map, &sorted_graph); @@ -343,7 +344,9 @@ struct graph_t auto* clone = vertices_.push (); auto& child = vertices_[child_idx]; clone_buffer_t* buffer = clone_buffers_.push (); - buffer->copy (child.obj); + if (!check_success (buffer->copy (child.obj))) { + return; + } clone->obj.head = buffer->head; clone->obj.tail = buffer->tail; @@ -352,6 +355,8 @@ struct graph_t for (const auto& l : child.obj.links) clone->obj.links.push (l); + check_success (!clone->obj.links.in_error ()); + auto& parent = vertices_[parent_idx]; unsigned clone_idx = vertices_.length - 2; for (unsigned i = 0; i < parent.obj.links.length; i++) @@ -433,8 +438,13 @@ struct graph_t } } + void err_other_error () { this->successful = false; } + private: + bool check_success (bool success) + { return this->successful && (success || (err_other_error (), false)); } + /* * Creates a map from objid to # of incoming edges. */ @@ -506,7 +516,7 @@ struct graph_t hb_set_t visited; - while (!queue.is_empty ()) + while (!queue.in_error () && !queue.is_empty ()) { unsigned next_idx = queue.extract_minimum ().first; if (visited.has (next_idx)) continue; @@ -530,8 +540,14 @@ struct graph_t } } } - // TODO(garretrieger): Handle this. If anything is left, part of the graph is disconnected. - assert (queue.is_empty ()); + + check_success (!queue.in_error ()); + if (!check_success (queue.is_empty ())) + { + DEBUG_MSG (SUBSET_REPACK, nullptr, "Graph is not fully connected."); + return; + } + distance_invalid = false; } @@ -636,6 +652,7 @@ struct graph_t bool edge_count_invalid; bool distance_invalid; bool positions_invalid; + bool successful; }; @@ -657,7 +674,9 @@ hb_resolve_overflows (const hb_vector_t& pac unsigned round = 0; hb_vector_t overflows; // TODO(garretrieger): select a good limit for max rounds. - while (sorted_graph.will_overflow (&overflows) && round++ < 10) { + while (!sorted_graph.in_error () + && sorted_graph.will_overflow (&overflows) + && round++ < 10) { DEBUG_MSG (SUBSET_REPACK, nullptr, "=== Over flow resolution round %d ===", round); sorted_graph.print_overflows (overflows); @@ -682,6 +701,12 @@ hb_resolve_overflows (const hb_vector_t& pac // TODO(garretrieger): initially limiting this to leaf's but likely // can be used for non-leafs as well. // TODO(garretrieger): add a maximum priority, don't try to raise past this. + // TODO(garretrieger): also try lowering priority of the parent. Make it + // get placed further up in the ordering, closer to it's children. + // this is probably preferable if the total size of the parent object + // is < then the total size of the children (and the parent can be moved). + // Since in that case moving the parent will cause a smaller increase in + // the length of other offsets. sorted_graph.raise_childrens_priority (r.parent); priority_bumped_parents.add (r.parent); resolution_attempted = true; @@ -700,9 +725,15 @@ hb_resolve_overflows (const hb_vector_t& pac } DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :("); - break; + c->err_offset_overflow (); + return; } + if (sorted_graph.in_error ()) + { + c->err_other_error (); + return; + } sorted_graph.serialize (c); } From 832f2b599b3d4fad5eea6d0eeef77377d3e0bad0 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Tue, 10 Nov 2020 16:15:37 -0800 Subject: [PATCH 20/30] [subset] Refactor _subset () to reduce nesting and eliminate the use of 'goto'. --- src/hb-serialize.hh | 9 +++ src/hb-subset.cc | 138 ++++++++++++++++++++++++++------------------ src/meson.build | 1 + 3 files changed, 92 insertions(+), 56 deletions(-) diff --git a/src/hb-serialize.hh b/src/hb-serialize.hh index 7afe61710..c8a209d67 100644 --- a/src/hb-serialize.hh +++ b/src/hb-serialize.hh @@ -119,6 +119,14 @@ struct hb_serialize_context_t bool in_error () const { return !this->successful; } + void reset (void *start_, unsigned int size) + { + start = (char*) start_; + end = start + size; + reset (); + current = nullptr; + } + void reset () { this->successful = true; @@ -129,6 +137,7 @@ struct hb_serialize_context_t fini (); this->packed.push (nullptr); + this->packed_map.init (); } bool check_success (bool success) diff --git a/src/hb-subset.cc b/src/hb-subset.cc index 46cfbe9d0..0d64de1c8 100644 --- a/src/hb-subset.cc +++ b/src/hb-subset.cc @@ -94,74 +94,100 @@ _repack (hb_tag_t tag, const hb_serialize_context_t& c) return repacked.copy_blob (); } +template +static +bool +_try_subset (const TableType *table, + hb_vector_t* buf, + unsigned buf_size, + hb_subset_context_t* c /* OUT */) +{ + c->serializer->start_serialize (); + + bool needed = table->subset (c); + if (!c->serializer->ran_out_of_room) + { + c->serializer->end_serialize (); + return needed; + } + + buf_size += (buf_size >> 1) + 32; + DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c ran out of room; reallocating to %u bytes.", + HB_UNTAG (c->table_tag), buf_size); + + if (unlikely (!buf->alloc (buf_size))) + { + DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c failed to reallocate %u bytes.", + HB_UNTAG (c->table_tag), buf_size); + return needed; + } + + c->serializer->reset (buf->arrayZ, buf_size); + return _try_subset (table, buf, buf_size, c); +} + template static bool _subset (hb_subset_plan_t *plan) { - bool result = false; hb_blob_t *source_blob = hb_sanitize_context_t ().reference_table (plan->source); const TableType *table = source_blob->as (); hb_tag_t tag = TableType::tableTag; - if (source_blob->data) + if (!source_blob->data) { - hb_vector_t buf; - /* TODO Not all tables are glyph-related. 'name' table size for example should not be - * affected by number of glyphs. Accommodate that. */ - unsigned buf_size = _plan_estimate_subset_table_size (plan, source_blob->length); - DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c initial estimated table size: %u bytes.", HB_UNTAG (tag), buf_size); - if (unlikely (!buf.alloc (buf_size))) - { - DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c failed to allocate %u bytes.", HB_UNTAG (tag), buf_size); - hb_blob_destroy (source_blob); - return false; - } - retry: - hb_serialize_context_t serializer ((void *) buf, buf_size); - serializer.start_serialize (); - hb_subset_context_t c (source_blob, plan, &serializer, tag); - bool needed = table->subset (&c); - if (serializer.ran_out_of_room) - { - buf_size += (buf_size >> 1) + 32; - DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c ran out of room; reallocating to %u bytes.", HB_UNTAG (tag), buf_size); - if (unlikely (!buf.alloc (buf_size))) - { - DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c failed to reallocate %u bytes.", HB_UNTAG (tag), buf_size); - hb_blob_destroy (source_blob); - return false; - } - goto retry; - } - serializer.end_serialize (); - - result = !serializer.in_error (); - - if (result) - { - if (needed) - { - hb_blob_t *dest_blob = _repack (tag, serializer); - if (dest_blob) - { - DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c final subset table size: %u bytes.", HB_UNTAG (tag), dest_blob->length); - result = c.plan->add_table (tag, dest_blob); - hb_blob_destroy (dest_blob); - } else { - result = false; - } - } - else - { - DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c::subset table subsetted to empty.", HB_UNTAG (tag)); - } - } + DEBUG_MSG (SUBSET, nullptr, + "OT::%c%c%c%c::subset sanitize failed on source table.", HB_UNTAG (tag)); + hb_blob_destroy (source_blob); + return false; } - else - DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c::subset sanitize failed on source table.", HB_UNTAG (tag)); + hb_vector_t buf; + /* TODO Not all tables are glyph-related. 'name' table size for example should not be + * affected by number of glyphs. Accommodate that. */ + unsigned buf_size = _plan_estimate_subset_table_size (plan, source_blob->length); + DEBUG_MSG (SUBSET, nullptr, + "OT::%c%c%c%c initial estimated table size: %u bytes.", HB_UNTAG (tag), buf_size); + if (unlikely (!buf.alloc (buf_size))) + { + DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c failed to allocate %u bytes.", HB_UNTAG (tag), buf_size); + hb_blob_destroy (source_blob); + return false; + } + + bool needed = false; + hb_serialize_context_t serializer (buf.arrayZ, buf_size); + { + hb_subset_context_t c (source_blob, plan, &serializer, tag); + needed = _try_subset (table, &buf, buf_size, &c); + } hb_blob_destroy (source_blob); - DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c::subset %s", HB_UNTAG (tag), result ? "success" : "FAILED!"); + + if (serializer.ran_out_of_room || serializer.in_error ()) + { + DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c::subset FAILED!", HB_UNTAG (tag)); + return false; + } + + if (!needed) + { + DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c::subset table subsetted to empty.", HB_UNTAG (tag)); + return true; + } + + bool result = false; + hb_blob_t *dest_blob = _repack (tag, serializer); + if (dest_blob) + { + DEBUG_MSG (SUBSET, nullptr, + "OT::%c%c%c%c final subset table size: %u bytes.", + HB_UNTAG (tag), dest_blob->length); + result = plan->add_table (tag, dest_blob); + hb_blob_destroy (dest_blob); + } + + DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c::subset %s", + HB_UNTAG (tag), result ? "success" : "FAILED!"); return result; } diff --git a/src/meson.build b/src/meson.build index 280ddb521..dddafe9c2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -478,6 +478,7 @@ if get_option('tests').enabled() 'test-algs': ['test-algs.cc', 'hb-static.cc'], 'test-array': ['test-array.cc'], 'test-repacker': ['test-repacker.cc', 'hb-static.cc'], + 'test-priority-queue': ['test-priority-queue.cc', 'hb-static.cc'], 'test-iter': ['test-iter.cc', 'hb-static.cc'], 'test-meta': ['test-meta.cc', 'hb-static.cc'], 'test-number': ['test-number.cc', 'hb-number.cc'], From d3e2ba7c01b26da9cc5fac49a204fae8a54a3eb1 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Wed, 11 Nov 2020 13:50:18 -0800 Subject: [PATCH 21/30] [subset] comment cleanup in hb-repacker.hh --- src/hb-repacker.hh | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index 75ba39132..0beb438db 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -288,9 +288,6 @@ struct graph_t check_success (removed_edges.resize (vertices_.length)); update_incoming_edge_count (); - // Object graphs are in reverse order, the first object is at the end - // of the vector. Since the graph is topologically sorted it's safe to - // assume the first object has no incoming edges. queue.insert (root_idx (), root ().modified_distance (0)); int new_id = root_idx (); unsigned order = 1; @@ -308,7 +305,7 @@ struct graph_t // Add the order that the links were encountered to the priority. // This ensures that ties between priorities objects are broken in a consistent // way. More specifically this is set up so that if a set of objects have the same - // distance they'll be added to the topolical order in the order that they are + // distance they'll be added to the topological order in the order that they are // referenced from the parent object. queue.insert (link.objidx, vertices_[link.objidx].modified_distance (order++)); @@ -486,7 +483,7 @@ struct graph_t } /* - * Finds the distance too each object in the graph + * Finds the distance to each object in the graph * from the initial node. */ void update_distances () @@ -657,17 +654,27 @@ struct graph_t /* - * Re-serialize the provided object graph into the serialization context - * using BFS (Breadth First Search) to produce the topological ordering. + * Attempts to modify the topological sorting of the provided object graph to + * eliminate offset overflows in the links between objects of the graph. If a + * non-overflowing ordering is found the updated graph is serialized it into the + * provided serialization context. + * + * If necessary the structure of the graph may be modified in ways that do not + * affect the functionality of the graph. For example shared objects may be + * duplicated. */ inline void hb_resolve_overflows (const hb_vector_t& packed, hb_serialize_context_t* c) { // Kahn sort is ~twice as fast as shortest distance sort and works for many fonts - // so try it first. + // so try it first to save time. graph_t sorted_graph (packed); sorted_graph.sort_kahn (); - if (!sorted_graph.will_overflow ()) return; + if (!sorted_graph.will_overflow ()) + { + sorted_graph.serialize (c); + return; + } sorted_graph.sort_shortest_distance (); @@ -693,13 +700,19 @@ hb_resolve_overflows (const hb_vector_t& pac // by duplicating it. sorted_graph.duplicate (r.parent, r.link->objidx); resolution_attempted = true; + + // Stop processing overflows for this round so that object order can be + // updated to account for the newly added object. break; } if (child.is_leaf () && !priority_bumped_parents.has (r.parent)) { - // TODO(garretrieger): initially limiting this to leaf's but likely - // can be used for non-leafs as well. + // This object is too far from it's parent, attempt to move it closer. + // + // TODO(garretrieger): initially limiting this to leaf's since they can be + // moved closer with fewer consequences. However, this can + // likely can be used for non-leafs as well. // TODO(garretrieger): add a maximum priority, don't try to raise past this. // TODO(garretrieger): also try lowering priority of the parent. Make it // get placed further up in the ordering, closer to it's children. From e2f14e81bd20cc944bdecba7fcea20a4b4eddec0 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Wed, 11 Nov 2020 13:55:35 -0800 Subject: [PATCH 22/30] [subset] fix memory leaks in test-repacker. --- src/test-repacker.cc | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/test-repacker.cc b/src/test-repacker.cc index 33067b252..1b4860394 100644 --- a/src/test-repacker.cc +++ b/src/test-repacker.cc @@ -211,6 +211,8 @@ static void test_sort_kahn_1 () assert(strncmp (graph.object (0).head, "ghi", 3) == 0); assert(graph.object (0).links.length == 0); + + free (buffer); } static void test_sort_kahn_2 () @@ -243,6 +245,8 @@ static void test_sort_kahn_2 () assert(strncmp (graph.object (0).head, "jkl", 3) == 0); assert(graph.object (0).links.length == 0); + + free (buffer); } static void test_sort_shortest () @@ -255,7 +259,6 @@ static void test_sort_shortest () graph_t graph (c.object_graph ()); graph.sort_shortest_distance (); - assert(strncmp (graph.object (4).head, "abc", 3) == 0); assert(graph.object (4).links.length == 3); assert(graph.object (4).links[0].objidx == 2); @@ -275,6 +278,8 @@ static void test_sort_shortest () assert(strncmp (graph.object (0).head, "jkl", 3) == 0); assert(graph.object (0).links.length == 0); + + free (buffer); } static void test_duplicate_leaf () @@ -309,6 +314,8 @@ static void test_duplicate_leaf () assert(strncmp (graph.object (0).head, "mn", 2) == 0); assert(graph.object (0).links.length == 0); + + free (buffer); } static void test_duplicate_interior () @@ -348,6 +355,8 @@ static void test_duplicate_interior () assert(strncmp (graph.object (0).head, "opqrst", 6) == 0); assert(graph.object (0).links.length == 0); + + free (buffer); } static void @@ -368,6 +377,8 @@ test_serialize () assert (actual == expected); + actual.free (); + expected.free (); free (buffer_1); free (buffer_2); } @@ -381,6 +392,8 @@ static void test_will_overflow_1 () graph_t graph (c.object_graph ()); assert (!graph.will_overflow (nullptr)); + + free (buffer); } static void test_will_overflow_2 () @@ -392,6 +405,8 @@ static void test_will_overflow_2 () graph_t graph (c.object_graph ()); assert (graph.will_overflow (nullptr)); + + free (buffer); } static void test_will_overflow_3 () @@ -403,6 +418,8 @@ static void test_will_overflow_3 () graph_t graph (c.object_graph ()); assert (graph.will_overflow (nullptr)); + + free (buffer); } static void test_resolve_overflows_via_sort () From 0104409959b15ca8dc344df4a60ce36fc7f40105 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Tue, 16 Feb 2021 11:38:14 -0800 Subject: [PATCH 23/30] Fix repack_tests for distcheck. --- test/subset/data/repack_tests/Makefile.am | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/subset/data/repack_tests/Makefile.am b/test/subset/data/repack_tests/Makefile.am index 8e814fb2c..f85af6a1b 100644 --- a/test/subset/data/repack_tests/Makefile.am +++ b/test/subset/data/repack_tests/Makefile.am @@ -5,10 +5,6 @@ EXTRA_DIST = CLEANFILES = SUBDIRS = -EXTRA_DIST += - $(TESTS) \ - $(NULL) - # Convenience targets: lib: libs # Always build subsetter lib in this subdir libs: @@ -18,5 +14,8 @@ TEST_EXTENSIONS = .tests TESTS_LOG_COMPILER = $(srcdir)/../../run-repack-tests.py $(top_builddir)/util/hb-subset$(EXEEXT) include Makefile.sources +EXTRA_DIST += \ + $(TESTS) \ + $(NULL) -include $(top_srcdir)/git.mk From cf79fc342d7e59966fc7ba3e8460d58083b33966 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Tue, 16 Feb 2021 13:24:43 -0800 Subject: [PATCH 24/30] [subset] limit priority bumps to 16. --- src/hb-repacker.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index 0beb438db..68165d4ce 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -83,7 +83,7 @@ struct graph_t { if (!priority) return 0; int64_t table_size = obj.tail - obj.head; - return -(table_size - table_size / (1 << priority)); + return -(table_size - table_size / (1 << hb_min(priority, 16u))); } }; From b9ecc7420df811e94413d77e6d70140e18d6ebe6 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Tue, 16 Feb 2021 13:39:10 -0800 Subject: [PATCH 25/30] [subset] init offset_overflow in hb_serialize_context_t. --- src/hb-serialize.hh | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hb-serialize.hh b/src/hb-serialize.hh index c8a209d67..77ed74731 100644 --- a/src/hb-serialize.hh +++ b/src/hb-serialize.hh @@ -131,6 +131,7 @@ struct hb_serialize_context_t { this->successful = true; this->ran_out_of_room = false; + this->offset_overflow = false; this->head = this->start; this->tail = this->end; this->debug_depth = 0; From 73ed59f7a68fc5363ef444b6de131c92cc5ca836 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Wed, 17 Mar 2021 15:53:10 -0700 Subject: [PATCH 26/30] [subset] store errors in the serializer as a flag set. Make check_assign/check_equal specify the type of error to set. --- src/hb-cff-interp-common.hh | 2 +- src/hb-open-type.hh | 10 +++--- src/hb-ot-cmap-table.hh | 17 ++++++--- src/hb-ot-hdmx-table.hh | 2 +- src/hb-ot-hmtx-table.hh | 2 +- src/hb-ot-layout-gpos-table.hh | 8 ++--- src/hb-ot-layout-gsub-table.hh | 4 +-- src/hb-ot-name-table.hh | 3 +- src/hb-repacker.hh | 4 +-- src/hb-serialize.hh | 64 +++++++++++++++++++++------------- src/hb-subset.cc | 8 ++--- src/test-repacker.cc | 4 +-- 12 files changed, 76 insertions(+), 52 deletions(-) diff --git a/src/hb-cff-interp-common.hh b/src/hb-cff-interp-common.hh index 91a9b7d0d..b53b544b0 100644 --- a/src/hb-cff-interp-common.hh +++ b/src/hb-cff-interp-common.hh @@ -263,7 +263,7 @@ struct UnsizedByteStr : UnsizedArrayOf T *ip = c->allocate_size (T::static_size); if (unlikely (!ip)) return_trace (false); - return_trace (c->check_assign (*ip, value)); + return_trace (c->check_assign (*ip, value, HB_SERIALIZE_ERR_OTHER)); } template diff --git a/src/hb-open-type.hh b/src/hb-open-type.hh index dc0ae1d98..f412d88dc 100644 --- a/src/hb-open-type.hh +++ b/src/hb-open-type.hh @@ -209,7 +209,9 @@ struct Offset : Type void *serialize (hb_serialize_context_t *c, const void *base) { void *t = c->start_embed (); - c->check_assign (*this, (unsigned) ((char *) t - (char *) base)); + c->check_assign (*this, + (unsigned) ((char *) t - (char *) base), + HB_SERIALIZE_ERR_OTHER); return t; } @@ -621,7 +623,7 @@ struct ArrayOf { TRACE_SERIALIZE (this); if (unlikely (!c->extend_min (*this))) return_trace (false); - c->check_assign (len, items_len); + c->check_assign (len, items_len, HB_SERIALIZE_ERR_OTHER); if (unlikely (!c->extend (*this))) return_trace (false); return_trace (true); } @@ -656,7 +658,7 @@ struct ArrayOf TRACE_SERIALIZE (this); auto *out = c->start_embed (this); if (unlikely (!c->extend_min (out))) return_trace (nullptr); - c->check_assign (out->len, len); + c->check_assign (out->len, len, HB_SERIALIZE_ERR_OTHER); if (unlikely (!as_array ().copy (c))) return_trace (nullptr); return_trace (out); } @@ -787,7 +789,7 @@ struct HeadlessArrayOf { TRACE_SERIALIZE (this); if (unlikely (!c->extend_min (*this))) return_trace (false); - c->check_assign (lenP1, items_len + 1); + c->check_assign (lenP1, items_len + 1, HB_SERIALIZE_ERR_OTHER); if (unlikely (!c->extend (*this))) return_trace (false); return_trace (true); } diff --git a/src/hb-ot-cmap-table.hh b/src/hb-ot-cmap-table.hh index 878e02ff1..650be0270 100644 --- a/src/hb-ot-cmap-table.hh +++ b/src/hb-ot-cmap-table.hh @@ -276,7 +276,9 @@ struct CmapSubtableFormat4 HBUINT16 *idRangeOffset = serialize_rangeoffset_glyid (c, format4_iter, endCode, startCode, idDelta, segcount); if (unlikely (!c->check_success (idRangeOffset))) return; - if (unlikely (!c->check_assign(this->length, c->length () - table_initpos))) return; + if (unlikely (!c->check_assign(this->length, + c->length () - table_initpos, + HB_SERIALIZE_ERR_OTHER))) return; this->segCountX2 = segcount * 2; this->entrySelector = hb_max (1u, hb_bit_storage (segcount)) - 1; this->searchRange = 2 * (1u << this->entrySelector); @@ -850,7 +852,9 @@ struct DefaultUVS : SortedArrayOf } else { - if (unlikely (!c->check_assign (out->len, (c->length () - init_len) / UnicodeValueRange::static_size))) return nullptr; + if (unlikely (!c->check_assign (out->len, + (c->length () - init_len) / UnicodeValueRange::static_size, + HB_SERIALIZE_ERR_OTHER))) return nullptr; return out; } } @@ -1112,10 +1116,11 @@ struct CmapSubtableFormat14 return; int tail_len = init_tail - c->tail; - c->check_assign (this->length, c->length () - table_initpos + tail_len); + c->check_assign (this->length, c->length () - table_initpos + tail_len, HB_SERIALIZE_ERR_OTHER); c->check_assign (this->record.len, (c->length () - table_initpos - CmapSubtableFormat14::min_size) / - VariationSelectorRecord::static_size); + VariationSelectorRecord::static_size, + HB_SERIALIZE_ERR_OTHER); /* Correct the incorrect write order by reversing the order of the variation records array. */ @@ -1401,7 +1406,9 @@ struct cmap } } - c->check_assign(this->encodingRecord.len, (c->length () - cmap::min_size)/EncodingRecord::static_size); + c->check_assign(this->encodingRecord.len, + (c->length () - cmap::min_size)/EncodingRecord::static_size, + HB_SERIALIZE_ERR_OTHER); } void closure_glyphs (const hb_set_t *unicodes, diff --git a/src/hb-ot-hdmx-table.hh b/src/hb-ot-hdmx-table.hh index c9c391bad..590fa154b 100644 --- a/src/hb-ot-hdmx-table.hh +++ b/src/hb-ot-hdmx-table.hh @@ -110,7 +110,7 @@ struct hdmx for (const hb_item_type& _ : +it) c->start_embed ()->serialize (c, _.first, _.second); - return_trace (c->successful); + return_trace (c->successful ()); } diff --git a/src/hb-ot-hmtx-table.hh b/src/hb-ot-hmtx-table.hh index d06c0fa4a..822c53176 100644 --- a/src/hb-ot-hmtx-table.hh +++ b/src/hb-ot-hmtx-table.hh @@ -146,7 +146,7 @@ struct hmtxvmtx _mtx.fini (); - if (unlikely (c->serializer->ran_out_of_room || c->serializer->in_error ())) + if (unlikely (c->serializer->ran_out_of_room () || c->serializer->in_error ())) return_trace (false); // Amend header num hmetrics diff --git a/src/hb-ot-layout-gpos-table.hh b/src/hb-ot-layout-gpos-table.hh index 72a44bf69..5c16786b2 100644 --- a/src/hb-ot-layout-gpos-table.hh +++ b/src/hb-ot-layout-gpos-table.hh @@ -694,7 +694,7 @@ struct MarkArray : ArrayOf /* Array of MarkRecords--in Coverage orde { TRACE_SERIALIZE (this); if (unlikely (!c->extend_min (*this))) return_trace (false); - if (unlikely (!c->check_assign (len, it.len ()))) return_trace (false); + if (unlikely (!c->check_assign (len, it.len (), HB_SERIALIZE_ERR_OTHER))) return_trace (false); c->copy_all (it, base, c->to_bias (this), klass_mapping, layout_variation_idx_map); return_trace (true); } @@ -756,7 +756,7 @@ struct SinglePosFormat1 { auto out = c->extend_min (*this); if (unlikely (!out)) return; - if (unlikely (!c->check_assign (valueFormat, valFormat))) return; + if (unlikely (!c->check_assign (valueFormat, valFormat, HB_SERIALIZE_ERR_OTHER))) return; + it | hb_map (hb_second) @@ -870,8 +870,8 @@ struct SinglePosFormat2 { auto out = c->extend_min (*this); if (unlikely (!out)) return; - if (unlikely (!c->check_assign (valueFormat, valFormat))) return; - if (unlikely (!c->check_assign (valueCount, it.len ()))) return; + if (unlikely (!c->check_assign (valueFormat, valFormat, HB_SERIALIZE_ERR_OTHER))) return; + if (unlikely (!c->check_assign (valueCount, it.len (), HB_SERIALIZE_ERR_OTHER))) return; + it | hb_map (hb_second) diff --git a/src/hb-ot-layout-gsub-table.hh b/src/hb-ot-layout-gsub-table.hh index ee95bbc00..132d008e9 100644 --- a/src/hb-ot-layout-gsub-table.hh +++ b/src/hb-ot-layout-gsub-table.hh @@ -102,7 +102,7 @@ struct SingleSubstFormat1 TRACE_SERIALIZE (this); if (unlikely (!c->extend_min (*this))) return_trace (false); if (unlikely (!coverage.serialize (c, this).serialize (c, glyphs))) return_trace (false); - c->check_assign (deltaGlyphID, delta); + c->check_assign (deltaGlyphID, delta, HB_SERIALIZE_ERR_OTHER); return_trace (true); } @@ -1551,7 +1551,7 @@ struct SubstLookup : Lookup template static inline typename context_t::return_t dispatch_recurse_func (context_t *c, unsigned int lookup_index); - + static inline typename hb_closure_context_t::return_t closure_glyphs_recurse_func (hb_closure_context_t *c, unsigned lookup_index, hb_set_t *covered_seq_indices, unsigned seq_index, unsigned end_index); static inline hb_closure_context_t::return_t dispatch_closure_recurse_func (hb_closure_context_t *c, unsigned lookup_index, hb_set_t *covered_seq_indices, unsigned seq_index, unsigned end_index) diff --git a/src/hb-ot-name-table.hh b/src/hb-ot-name-table.hh index ece3c2846..794a7cdb6 100644 --- a/src/hb-ot-name-table.hh +++ b/src/hb-ot-name-table.hh @@ -230,7 +230,8 @@ struct name c->copy_all (records, src_string_pool); free (records.arrayZ); - if (unlikely (c->ran_out_of_room)) return_trace (false); + + if (unlikely (c->ran_out_of_room ())) return_trace (false); this->stringOffset = c->length (); diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index 68165d4ce..15ed61bbe 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -738,13 +738,13 @@ hb_resolve_overflows (const hb_vector_t& pac } DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :("); - c->err_offset_overflow (); + c->set_error (HB_SERIALIZE_ERR_OFFSET_OVERFLOW); return; } if (sorted_graph.in_error ()) { - c->err_other_error (); + c->set_error (HB_SERIALIZE_ERR_OTHER); return; } sorted_graph.serialize (c); diff --git a/src/hb-serialize.hh b/src/hb-serialize.hh index 77ed74731..f9895e1fd 100644 --- a/src/hb-serialize.hh +++ b/src/hb-serialize.hh @@ -41,6 +41,14 @@ * Serialize */ +enum hb_serialize_error_type_t { + HB_SERIALIZE_ERR_NONE = 0x00000000u, + HB_SERIALIZE_ERR_OTHER = 0x00000001u, + HB_SERIALIZE_ERR_OFFSET_OVERFLOW = 0x00000002u, + HB_SERIALIZE_ERR_OUT_OF_ROOM = 0x00000004u, +}; +HB_MARK_AS_FLAG_T (hb_serialize_error_type_t); + struct hb_serialize_context_t { typedef unsigned objidx_t; @@ -51,6 +59,8 @@ struct hb_serialize_context_t Absolute /* Absolute: from the start of the serialize buffer. */ }; + + struct object_t { void fini () { links.fini (); } @@ -117,7 +127,12 @@ struct hb_serialize_context_t object_pool.fini (); } - bool in_error () const { return !this->successful; } + bool in_error () const { return errors & HB_SERIALIZE_ERR_OTHER; } + + bool successful () const { return !in_error (); } + + bool ran_out_of_room () const { return errors & HB_SERIALIZE_ERR_OUT_OF_ROOM; } + bool offset_overflow () const { return errors & HB_SERIALIZE_ERR_OFFSET_OVERFLOW; } void reset (void *start_, unsigned int size) { @@ -129,9 +144,7 @@ struct hb_serialize_context_t void reset () { - this->successful = true; - this->ran_out_of_room = false; - this->offset_overflow = false; + this->errors = HB_SERIALIZE_ERR_NONE; this->head = this->start; this->tail = this->end; this->debug_depth = 0; @@ -141,23 +154,27 @@ struct hb_serialize_context_t this->packed_map.init (); } - bool check_success (bool success) - { return this->successful && (success || (err_other_error (), false)); } + bool check_success (bool success, + hb_serialize_error_type_t err_type = HB_SERIALIZE_ERR_OTHER) + { + return successful () + && (success || (set_error (err_type), false)); + } template - bool check_equal (T1 &&v1, T2 &&v2) + bool check_equal (T1 &&v1, T2 &&v2, hb_serialize_error_type_t err_type) { if ((long long) v1 != (long long) v2) { - err_offset_overflow (); + set_error (err_type); return false; } return true; } template - bool check_assign (T1 &v1, T2 &&v2) - { return check_equal (v1 = v2, v2); } + bool check_assign (T1 &v1, T2 &&v2, hb_serialize_error_type_t err_type) + { return check_equal (v1 = v2, v2, err_type); } template bool propagate_error (T &&obj) { return check_success (!hb_deref (obj).in_error ()); } @@ -184,7 +201,7 @@ struct hb_serialize_context_t "end [%p..%p] serialized %u bytes; %s", this->start, this->end, (unsigned) (this->head - this->start), - this->successful ? "successful" : "UNSUCCESSFUL"); + successful () ? "successful" : "UNSUCCESSFUL"); propagate_error (packed, packed_map); @@ -368,7 +385,7 @@ struct hb_serialize_context_t for (const object_t::link_t &link : parent->links) { const object_t* child = packed[link.objidx]; - if (unlikely (!child)) { err_other_error(); return; } + if (unlikely (!child)) { set_error (HB_SERIALIZE_ERR_OTHER); return; } unsigned offset = 0; switch ((whence_t) link.whence) { case Head: offset = child->head - parent->head; break; @@ -415,20 +432,20 @@ struct hb_serialize_context_t Type *start_embed (const Type &obj) const { return start_embed (hb_addressof (obj)); } - /* Following two functions exist to allow setting breakpoint on. */ - void err_ran_out_of_room () { this->ran_out_of_room = true; } - void err_offset_overflow () { this->offset_overflow = true; } - void err_other_error () { this->successful = false; } + void set_error (hb_serialize_error_type_t err_type) + { + errors = errors | err_type; + } template Type *allocate_size (unsigned int size) { - if (unlikely (!this->successful)) return nullptr; + if (unlikely (in_error ())) return nullptr; if (this->tail - this->head < ptrdiff_t (size)) { - err_ran_out_of_room (); - this->successful = false; + set_error (HB_SERIALIZE_ERR_OUT_OF_ROOM); + set_error (HB_SERIALIZE_ERR_OTHER); return nullptr; } memset (this->head, 0, size); @@ -515,7 +532,7 @@ struct hb_serialize_context_t /* Output routines. */ hb_bytes_t copy_bytes () const { - assert (this->successful); + assert (successful ()); /* Copy both items from head side and tail side... */ unsigned int len = (this->head - this->start) + (this->end - this->tail); @@ -547,15 +564,13 @@ struct hb_serialize_context_t { auto &off = * ((BEInt *) (parent->head + link.position)); assert (0 == off); - check_assign (off, offset); + check_assign (off, offset, HB_SERIALIZE_ERR_OFFSET_OVERFLOW); } public: /* TODO Make private. */ char *start, *head, *tail, *end; unsigned int debug_depth; - bool successful; - bool ran_out_of_room; - bool offset_overflow; + hb_serialize_error_type_t errors; private: @@ -572,5 +587,4 @@ struct hb_serialize_context_t hb_hashmap_t packed_map; }; - #endif /* HB_SERIALIZE_HH */ diff --git a/src/hb-subset.cc b/src/hb-subset.cc index 0d64de1c8..0ba77b439 100644 --- a/src/hb-subset.cc +++ b/src/hb-subset.cc @@ -75,7 +75,7 @@ _repack (hb_tag_t tag, const hb_serialize_context_t& c) && tag != HB_OT_TAG_GSUB) return c.copy_blob (); - if (!c.offset_overflow) + if (!c.offset_overflow ()) return c.copy_blob (); hb_vector_t buf; @@ -86,7 +86,7 @@ _repack (hb_tag_t tag, const hb_serialize_context_t& c) hb_serialize_context_t repacked ((void *) buf, buf_size); hb_resolve_overflows (c.object_graph (), &repacked); - if (unlikely (repacked.ran_out_of_room || repacked.in_error () || repacked.offset_overflow)) + if (unlikely (repacked.ran_out_of_room () || repacked.in_error () || repacked.offset_overflow ())) // TODO(garretrieger): refactor so we can share the resize/retry logic with the subset // portion. return nullptr; @@ -105,7 +105,7 @@ _try_subset (const TableType *table, c->serializer->start_serialize (); bool needed = table->subset (c); - if (!c->serializer->ran_out_of_room) + if (!c->serializer->ran_out_of_room ()) { c->serializer->end_serialize (); return needed; @@ -163,7 +163,7 @@ _subset (hb_subset_plan_t *plan) } hb_blob_destroy (source_blob); - if (serializer.ran_out_of_room || serializer.in_error ()) + if (serializer.ran_out_of_room () || serializer.in_error ()) { DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c::subset FAILED!", HB_UNTAG (tag)); return false; diff --git a/src/test-repacker.cc b/src/test-repacker.cc index 1b4860394..a8cc6395f 100644 --- a/src/test-repacker.cc +++ b/src/test-repacker.cc @@ -434,7 +434,7 @@ static void test_resolve_overflows_via_sort () hb_serialize_context_t out (out_buffer, buffer_size); hb_resolve_overflows (c.object_graph (), &out); - assert (!out.offset_overflow); + assert (!out.offset_overflow ()); hb_bytes_t result = out.copy_bytes (); assert (result.length == (80000 + 3 + 3 * 2)); @@ -455,7 +455,7 @@ static void test_resolve_overflows_via_duplication () hb_serialize_context_t out (out_buffer, buffer_size); hb_resolve_overflows (c.object_graph (), &out); - assert (!out.offset_overflow); + assert (!out.offset_overflow ()); hb_bytes_t result = out.copy_bytes (); assert (result.length == (10000 + 2 * 2 + 60000 + 2 + 3 * 2)); From b14475d2ae488acf3c2a169126a4901796401157 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 18 Mar 2021 10:51:26 -0700 Subject: [PATCH 27/30] [subset] further changes to serializer error handling. - Rename enum type and enum members. - in_errors() now returns true for any error having been set. hb-subset now looks for offset overflow only errors to divert to repacker. - Added INT_OVERFLOW and ARRAY_OVERFLOW enum values. --- src/hb-cff-interp-common.hh | 2 +- src/hb-open-type.hh | 8 +++--- src/hb-ot-cmap-table.hh | 11 ++++---- src/hb-ot-hmtx-table.hh | 2 +- src/hb-ot-layout-gpos-table.hh | 8 +++--- src/hb-ot-layout-gsub-table.hh | 2 +- src/hb-repacker.hh | 4 +-- src/hb-serialize.hh | 50 ++++++++++++++++++++-------------- src/hb-subset.cc | 4 +-- 9 files changed, 50 insertions(+), 41 deletions(-) diff --git a/src/hb-cff-interp-common.hh b/src/hb-cff-interp-common.hh index b53b544b0..c251e2d0e 100644 --- a/src/hb-cff-interp-common.hh +++ b/src/hb-cff-interp-common.hh @@ -263,7 +263,7 @@ struct UnsizedByteStr : UnsizedArrayOf T *ip = c->allocate_size (T::static_size); if (unlikely (!ip)) return_trace (false); - return_trace (c->check_assign (*ip, value, HB_SERIALIZE_ERR_OTHER)); + return_trace (c->check_assign (*ip, value, HB_SERIALIZE_ERROR_INT_OVERFLOW)); } template diff --git a/src/hb-open-type.hh b/src/hb-open-type.hh index f412d88dc..297edf08e 100644 --- a/src/hb-open-type.hh +++ b/src/hb-open-type.hh @@ -211,7 +211,7 @@ struct Offset : Type void *t = c->start_embed (); c->check_assign (*this, (unsigned) ((char *) t - (char *) base), - HB_SERIALIZE_ERR_OTHER); + HB_SERIALIZE_ERROR_OFFSET_OVERFLOW); return t; } @@ -623,7 +623,7 @@ struct ArrayOf { TRACE_SERIALIZE (this); if (unlikely (!c->extend_min (*this))) return_trace (false); - c->check_assign (len, items_len, HB_SERIALIZE_ERR_OTHER); + c->check_assign (len, items_len, HB_SERIALIZE_ERROR_ARRAY_OVERFLOW); if (unlikely (!c->extend (*this))) return_trace (false); return_trace (true); } @@ -658,7 +658,7 @@ struct ArrayOf TRACE_SERIALIZE (this); auto *out = c->start_embed (this); if (unlikely (!c->extend_min (out))) return_trace (nullptr); - c->check_assign (out->len, len, HB_SERIALIZE_ERR_OTHER); + c->check_assign (out->len, len, HB_SERIALIZE_ERROR_ARRAY_OVERFLOW); if (unlikely (!as_array ().copy (c))) return_trace (nullptr); return_trace (out); } @@ -789,7 +789,7 @@ struct HeadlessArrayOf { TRACE_SERIALIZE (this); if (unlikely (!c->extend_min (*this))) return_trace (false); - c->check_assign (lenP1, items_len + 1, HB_SERIALIZE_ERR_OTHER); + c->check_assign (lenP1, items_len + 1, HB_SERIALIZE_ERROR_ARRAY_OVERFLOW); if (unlikely (!c->extend (*this))) return_trace (false); return_trace (true); } diff --git a/src/hb-ot-cmap-table.hh b/src/hb-ot-cmap-table.hh index 650be0270..97cd0f526 100644 --- a/src/hb-ot-cmap-table.hh +++ b/src/hb-ot-cmap-table.hh @@ -278,7 +278,7 @@ struct CmapSubtableFormat4 if (unlikely (!c->check_assign(this->length, c->length () - table_initpos, - HB_SERIALIZE_ERR_OTHER))) return; + HB_SERIALIZE_ERROR_INT_OVERFLOW))) return; this->segCountX2 = segcount * 2; this->entrySelector = hb_max (1u, hb_bit_storage (segcount)) - 1; this->searchRange = 2 * (1u << this->entrySelector); @@ -854,7 +854,7 @@ struct DefaultUVS : SortedArrayOf { if (unlikely (!c->check_assign (out->len, (c->length () - init_len) / UnicodeValueRange::static_size, - HB_SERIALIZE_ERR_OTHER))) return nullptr; + HB_SERIALIZE_ERROR_INT_OVERFLOW))) return nullptr; return out; } } @@ -1116,11 +1116,12 @@ struct CmapSubtableFormat14 return; int tail_len = init_tail - c->tail; - c->check_assign (this->length, c->length () - table_initpos + tail_len, HB_SERIALIZE_ERR_OTHER); + c->check_assign (this->length, c->length () - table_initpos + tail_len, + HB_SERIALIZE_ERROR_INT_OVERFLOW); c->check_assign (this->record.len, (c->length () - table_initpos - CmapSubtableFormat14::min_size) / VariationSelectorRecord::static_size, - HB_SERIALIZE_ERR_OTHER); + HB_SERIALIZE_ERROR_INT_OVERFLOW); /* Correct the incorrect write order by reversing the order of the variation records array. */ @@ -1408,7 +1409,7 @@ struct cmap c->check_assign(this->encodingRecord.len, (c->length () - cmap::min_size)/EncodingRecord::static_size, - HB_SERIALIZE_ERR_OTHER); + HB_SERIALIZE_ERROR_INT_OVERFLOW); } void closure_glyphs (const hb_set_t *unicodes, diff --git a/src/hb-ot-hmtx-table.hh b/src/hb-ot-hmtx-table.hh index 822c53176..403832993 100644 --- a/src/hb-ot-hmtx-table.hh +++ b/src/hb-ot-hmtx-table.hh @@ -146,7 +146,7 @@ struct hmtxvmtx _mtx.fini (); - if (unlikely (c->serializer->ran_out_of_room () || c->serializer->in_error ())) + if (unlikely (c->serializer->in_error ())) return_trace (false); // Amend header num hmetrics diff --git a/src/hb-ot-layout-gpos-table.hh b/src/hb-ot-layout-gpos-table.hh index 5c16786b2..7c8c85777 100644 --- a/src/hb-ot-layout-gpos-table.hh +++ b/src/hb-ot-layout-gpos-table.hh @@ -694,7 +694,7 @@ struct MarkArray : ArrayOf /* Array of MarkRecords--in Coverage orde { TRACE_SERIALIZE (this); if (unlikely (!c->extend_min (*this))) return_trace (false); - if (unlikely (!c->check_assign (len, it.len (), HB_SERIALIZE_ERR_OTHER))) return_trace (false); + if (unlikely (!c->check_assign (len, it.len (), HB_SERIALIZE_ERROR_ARRAY_OVERFLOW))) return_trace (false); c->copy_all (it, base, c->to_bias (this), klass_mapping, layout_variation_idx_map); return_trace (true); } @@ -756,7 +756,7 @@ struct SinglePosFormat1 { auto out = c->extend_min (*this); if (unlikely (!out)) return; - if (unlikely (!c->check_assign (valueFormat, valFormat, HB_SERIALIZE_ERR_OTHER))) return; + if (unlikely (!c->check_assign (valueFormat, valFormat, HB_SERIALIZE_ERROR_INT_OVERFLOW))) return; + it | hb_map (hb_second) @@ -870,8 +870,8 @@ struct SinglePosFormat2 { auto out = c->extend_min (*this); if (unlikely (!out)) return; - if (unlikely (!c->check_assign (valueFormat, valFormat, HB_SERIALIZE_ERR_OTHER))) return; - if (unlikely (!c->check_assign (valueCount, it.len (), HB_SERIALIZE_ERR_OTHER))) return; + if (unlikely (!c->check_assign (valueFormat, valFormat, HB_SERIALIZE_ERROR_INT_OVERFLOW))) return; + if (unlikely (!c->check_assign (valueCount, it.len (), HB_SERIALIZE_ERROR_ARRAY_OVERFLOW))) return; + it | hb_map (hb_second) diff --git a/src/hb-ot-layout-gsub-table.hh b/src/hb-ot-layout-gsub-table.hh index 132d008e9..062eea6ff 100644 --- a/src/hb-ot-layout-gsub-table.hh +++ b/src/hb-ot-layout-gsub-table.hh @@ -102,7 +102,7 @@ struct SingleSubstFormat1 TRACE_SERIALIZE (this); if (unlikely (!c->extend_min (*this))) return_trace (false); if (unlikely (!coverage.serialize (c, this).serialize (c, glyphs))) return_trace (false); - c->check_assign (deltaGlyphID, delta, HB_SERIALIZE_ERR_OTHER); + c->check_assign (deltaGlyphID, delta, HB_SERIALIZE_ERROR_INT_OVERFLOW); return_trace (true); } diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index 15ed61bbe..2b1e0fc56 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -738,13 +738,13 @@ hb_resolve_overflows (const hb_vector_t& pac } DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :("); - c->set_error (HB_SERIALIZE_ERR_OFFSET_OVERFLOW); + c->set_error (HB_SERIALIZE_ERROR_OFFSET_OVERFLOW); return; } if (sorted_graph.in_error ()) { - c->set_error (HB_SERIALIZE_ERR_OTHER); + c->set_error (HB_SERIALIZE_ERROR_OTHER); return; } sorted_graph.serialize (c); diff --git a/src/hb-serialize.hh b/src/hb-serialize.hh index f9895e1fd..6d61cd664 100644 --- a/src/hb-serialize.hh +++ b/src/hb-serialize.hh @@ -41,13 +41,15 @@ * Serialize */ -enum hb_serialize_error_type_t { - HB_SERIALIZE_ERR_NONE = 0x00000000u, - HB_SERIALIZE_ERR_OTHER = 0x00000001u, - HB_SERIALIZE_ERR_OFFSET_OVERFLOW = 0x00000002u, - HB_SERIALIZE_ERR_OUT_OF_ROOM = 0x00000004u, +enum hb_serialize_error_t { + HB_SERIALIZE_ERROR_NONE = 0x00000000u, + HB_SERIALIZE_ERROR_OTHER = 0x00000001u, + HB_SERIALIZE_ERROR_OFFSET_OVERFLOW = 0x00000002u, + HB_SERIALIZE_ERROR_OUT_OF_ROOM = 0x00000004u, + HB_SERIALIZE_ERROR_INT_OVERFLOW = 0x00000008u, + HB_SERIALIZE_ERROR_ARRAY_OVERFLOW = 0x00000010u }; -HB_MARK_AS_FLAG_T (hb_serialize_error_type_t); +HB_MARK_AS_FLAG_T (hb_serialize_error_t); struct hb_serialize_context_t { @@ -127,12 +129,13 @@ struct hb_serialize_context_t object_pool.fini (); } - bool in_error () const { return errors & HB_SERIALIZE_ERR_OTHER; } + bool in_error () const { return bool (errors); } - bool successful () const { return !in_error (); } + bool successful () const { return !bool (errors); } - bool ran_out_of_room () const { return errors & HB_SERIALIZE_ERR_OUT_OF_ROOM; } - bool offset_overflow () const { return errors & HB_SERIALIZE_ERR_OFFSET_OVERFLOW; } + bool ran_out_of_room () const { return errors & HB_SERIALIZE_ERROR_OUT_OF_ROOM; } + bool offset_overflow () const { return errors & HB_SERIALIZE_ERROR_OFFSET_OVERFLOW; } + bool only_offset_overflow () const { return errors == HB_SERIALIZE_ERROR_OFFSET_OVERFLOW; } void reset (void *start_, unsigned int size) { @@ -144,7 +147,7 @@ struct hb_serialize_context_t void reset () { - this->errors = HB_SERIALIZE_ERR_NONE; + this->errors = HB_SERIALIZE_ERROR_NONE; this->head = this->start; this->tail = this->end; this->debug_depth = 0; @@ -155,14 +158,14 @@ struct hb_serialize_context_t } bool check_success (bool success, - hb_serialize_error_type_t err_type = HB_SERIALIZE_ERR_OTHER) + hb_serialize_error_t err_type = HB_SERIALIZE_ERROR_OTHER) { return successful () && (success || (set_error (err_type), false)); } template - bool check_equal (T1 &&v1, T2 &&v2, hb_serialize_error_type_t err_type) + bool check_equal (T1 &&v1, T2 &&v2, hb_serialize_error_t err_type) { if ((long long) v1 != (long long) v2) { @@ -173,7 +176,7 @@ struct hb_serialize_context_t } template - bool check_assign (T1 &v1, T2 &&v2, hb_serialize_error_type_t err_type) + bool check_assign (T1 &v1, T2 &&v2, hb_serialize_error_t err_type) { return check_equal (v1 = v2, v2, err_type); } template bool propagate_error (T &&obj) @@ -206,7 +209,13 @@ struct hb_serialize_context_t propagate_error (packed, packed_map); if (unlikely (!current)) return; - if (unlikely (in_error())) return; + if (unlikely (in_error())) + { + // Offset overflows that occur before link resolution cannot be handled + // by repacking, so set a more general error. + if (offset_overflow ()) set_error (HB_SERIALIZE_ERROR_OTHER); + return; + } assert (!current->next); @@ -385,7 +394,7 @@ struct hb_serialize_context_t for (const object_t::link_t &link : parent->links) { const object_t* child = packed[link.objidx]; - if (unlikely (!child)) { set_error (HB_SERIALIZE_ERR_OTHER); return; } + if (unlikely (!child)) { set_error (HB_SERIALIZE_ERROR_OTHER); return; } unsigned offset = 0; switch ((whence_t) link.whence) { case Head: offset = child->head - parent->head; break; @@ -432,7 +441,7 @@ struct hb_serialize_context_t Type *start_embed (const Type &obj) const { return start_embed (hb_addressof (obj)); } - void set_error (hb_serialize_error_type_t err_type) + void set_error (hb_serialize_error_t err_type) { errors = errors | err_type; } @@ -444,8 +453,7 @@ struct hb_serialize_context_t if (this->tail - this->head < ptrdiff_t (size)) { - set_error (HB_SERIALIZE_ERR_OUT_OF_ROOM); - set_error (HB_SERIALIZE_ERR_OTHER); + set_error (HB_SERIALIZE_ERROR_OUT_OF_ROOM); return nullptr; } memset (this->head, 0, size); @@ -564,13 +572,13 @@ struct hb_serialize_context_t { auto &off = * ((BEInt *) (parent->head + link.position)); assert (0 == off); - check_assign (off, offset, HB_SERIALIZE_ERR_OFFSET_OVERFLOW); + check_assign (off, offset, HB_SERIALIZE_ERROR_OFFSET_OVERFLOW); } public: /* TODO Make private. */ char *start, *head, *tail, *end; unsigned int debug_depth; - hb_serialize_error_type_t errors; + hb_serialize_error_t errors; private: diff --git a/src/hb-subset.cc b/src/hb-subset.cc index 0ba77b439..57915f677 100644 --- a/src/hb-subset.cc +++ b/src/hb-subset.cc @@ -86,7 +86,7 @@ _repack (hb_tag_t tag, const hb_serialize_context_t& c) hb_serialize_context_t repacked ((void *) buf, buf_size); hb_resolve_overflows (c.object_graph (), &repacked); - if (unlikely (repacked.ran_out_of_room () || repacked.in_error () || repacked.offset_overflow ())) + if (unlikely (repacked.in_error ())) // TODO(garretrieger): refactor so we can share the resize/retry logic with the subset // portion. return nullptr; @@ -163,7 +163,7 @@ _subset (hb_subset_plan_t *plan) } hb_blob_destroy (source_blob); - if (serializer.ran_out_of_room () || serializer.in_error ()) + if (serializer.in_error () && !serializer.only_offset_overflow ()) { DEBUG_MSG (SUBSET, nullptr, "OT::%c%c%c%c::subset FAILED!", HB_UNTAG (tag)); return false; From f561fa6e4c5572c60c8fcf40e617919e531e6ee3 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 18 Mar 2021 11:13:47 -0700 Subject: [PATCH 28/30] Change priority queue to use (priority, value) instead of (value, priority). --- src/Makefile.sources | 2 +- src/hb-priority-queue.hh | 24 ++++++++++++----------- src/hb-repacker.hh | 14 ++++++------- src/test-priority-queue.cc | 40 +++++++++++++++++++------------------- 4 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/Makefile.sources b/src/Makefile.sources index da97530fe..14c97996b 100644 --- a/src/Makefile.sources +++ b/src/Makefile.sources @@ -269,7 +269,7 @@ HB_SUBSET_sources = \ hb-subset-plan.hh \ hb-subset.cc \ hb-subset.hh \ - hb-repacker.hh \ + hb-repacker.hh \ $(NULL) HB_SUBSET_headers = \ diff --git a/src/hb-priority-queue.hh b/src/hb-priority-queue.hh index e47127edb..7d799ae90 100644 --- a/src/hb-priority-queue.hh +++ b/src/hb-priority-queue.hh @@ -43,7 +43,7 @@ struct hb_priority_queue_t ~hb_priority_queue_t () { fini (); } private: - typedef hb_pair_t item_t; + typedef hb_pair_t item_t; hb_vector_t heap; public: @@ -55,13 +55,13 @@ struct hb_priority_queue_t bool in_error () const { return heap.in_error (); } - void insert (unsigned value, int64_t priority) + void insert (int64_t priority, unsigned value) { - heap.push (item_t (value, priority)); + heap.push (item_t (priority, value)); bubble_up (heap.length - 1); } - item_t extract_minimum () + item_t pop_minimum () { item_t result = heap[0]; @@ -78,6 +78,8 @@ struct hb_priority_queue_t } bool is_empty () const { return heap.length == 0; } + explicit operator bool () const { return !is_empty (); } + unsigned int get_population () const { return heap.length; } /* Sink interface. */ hb_priority_queue_t& operator << (item_t item) @@ -85,17 +87,17 @@ struct hb_priority_queue_t private: - unsigned parent (unsigned index) + static constexpr unsigned parent (unsigned index) { return (index - 1) / 2; } - unsigned left_child (unsigned index) + static constexpr unsigned left_child (unsigned index) { return 2 * index + 1; } - unsigned right_child (unsigned index) + static constexpr unsigned right_child (unsigned index) { return 2 * index + 2; } @@ -111,11 +113,11 @@ struct hb_priority_queue_t return; bool has_right = right < heap.length; - if (heap[index].second <= heap[left].second - && (!has_right || heap[index].second <= heap[right].second)) + if (heap[index].first <= heap[left].first + && (!has_right || heap[index].first <= heap[right].first)) return; - if (!has_right || heap[left].second < heap[right].second) + if (!has_right || heap[left].first < heap[right].first) { swap (index, left); bubble_down (left); @@ -131,7 +133,7 @@ struct hb_priority_queue_t if (index == 0) return; unsigned parent_index = parent (index); - if (heap[parent_index].second <= heap[index].second) + if (heap[parent_index].first <= heap[index].first) return; swap (index, parent_index); diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index 2b1e0fc56..cb0f8d613 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -288,12 +288,12 @@ struct graph_t check_success (removed_edges.resize (vertices_.length)); update_incoming_edge_count (); - queue.insert (root_idx (), root ().modified_distance (0)); + queue.insert (root ().modified_distance (0), root_idx ()); int new_id = root_idx (); unsigned order = 1; while (!queue.in_error () && !queue.is_empty ()) { - unsigned next_id = queue.extract_minimum().first; + unsigned next_id = queue.pop_minimum().second; vertex_t& next = vertices_[next_id]; sorted_graph.push (next); @@ -307,8 +307,8 @@ struct graph_t // way. More specifically this is set up so that if a set of objects have the same // distance they'll be added to the topological order in the order that they are // referenced from the parent object. - queue.insert (link.objidx, - vertices_[link.objidx].modified_distance (order++)); + queue.insert (vertices_[link.objidx].modified_distance (order++), + link.objidx); } } @@ -509,13 +509,13 @@ struct graph_t } hb_priority_queue_t queue; - queue.insert (vertices_.length - 1, 0); + queue.insert (0, vertices_.length - 1); hb_set_t visited; while (!queue.in_error () && !queue.is_empty ()) { - unsigned next_idx = queue.extract_minimum ().first; + unsigned next_idx = queue.pop_minimum ().second; if (visited.has (next_idx)) continue; const auto& next = vertices_[next_idx]; int64_t next_distance = vertices_[next_idx].distance; @@ -533,7 +533,7 @@ struct graph_t if (child_distance < vertices_[link.objidx].distance) { vertices_[link.objidx].distance = child_distance; - queue.insert (link.objidx, child_distance); + queue.insert (child_distance, link.objidx); } } } diff --git a/src/test-priority-queue.cc b/src/test-priority-queue.cc index 65c2a320b..fab63acb6 100644 --- a/src/test-priority-queue.cc +++ b/src/test-priority-queue.cc @@ -33,21 +33,21 @@ test_insert () hb_priority_queue_t queue; assert (queue.is_empty ()); - queue.insert (0, 10); + queue.insert (10, 0); assert (!queue.is_empty ()); - assert (queue.minimum () == hb_pair (0, 10)); + assert (queue.minimum () == hb_pair (10, 0)); - queue.insert (1, 20); - assert (queue.minimum () == hb_pair (0, 10)); + queue.insert (20, 1); + assert (queue.minimum () == hb_pair (10, 0)); - queue.insert (2, 5); - assert (queue.minimum () == hb_pair (2, 5)); + queue.insert (5, 2); + assert (queue.minimum () == hb_pair (5, 2)); - queue.insert (3, 15); - assert (queue.minimum () == hb_pair (2, 5)); + queue.insert (15, 3); + assert (queue.minimum () == hb_pair (5, 2)); - queue.insert (4, 1); - assert (queue.minimum () == hb_pair (4, 1)); + queue.insert (1, 4); + assert (queue.minimum () == hb_pair (1, 4)); } static void @@ -55,19 +55,19 @@ test_extract () { hb_priority_queue_t queue; queue.insert (0, 0); - queue.insert (6, 60); - queue.insert (3, 30); - queue.insert (4, 40); - queue.insert (2, 20); - queue.insert (5, 50); - queue.insert (7, 70); - queue.insert (1, 10); + queue.insert (60, 6); + queue.insert (30, 3); + queue.insert (40 ,4); + queue.insert (20, 2); + queue.insert (50, 5); + queue.insert (70, 7); + queue.insert (10, 1); for (int i = 0; i < 8; i++) { assert (!queue.is_empty ()); - assert (queue.minimum () == hb_pair (i, i * 10)); - assert (queue.extract_minimum () == hb_pair (i, i * 10)); + assert (queue.minimum () == hb_pair (i * 10, i)); + assert (queue.pop_minimum () == hb_pair (i * 10, i)); } assert (queue.is_empty ()); @@ -77,7 +77,7 @@ static void test_extract_empty () { hb_priority_queue_t queue; - assert (queue.extract_minimum () == hb_pair (0, 0)); + assert (queue.pop_minimum () == hb_pair (0, 0)); } int From 3827a3eb567b424e8144564a42a22df74a20a7c6 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 18 Mar 2021 11:20:03 -0700 Subject: [PATCH 29/30] [subset] rename serializer::set_error() to err(). --- src/hb-repacker.hh | 4 ++-- src/hb-serialize.hh | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index cb0f8d613..35e3566e5 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -738,13 +738,13 @@ hb_resolve_overflows (const hb_vector_t& pac } DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :("); - c->set_error (HB_SERIALIZE_ERROR_OFFSET_OVERFLOW); + c->err (HB_SERIALIZE_ERROR_OFFSET_OVERFLOW); return; } if (sorted_graph.in_error ()) { - c->set_error (HB_SERIALIZE_ERROR_OTHER); + c->err (HB_SERIALIZE_ERROR_OTHER); return; } sorted_graph.serialize (c); diff --git a/src/hb-serialize.hh b/src/hb-serialize.hh index 6d61cd664..a4303fc34 100644 --- a/src/hb-serialize.hh +++ b/src/hb-serialize.hh @@ -161,7 +161,7 @@ struct hb_serialize_context_t hb_serialize_error_t err_type = HB_SERIALIZE_ERROR_OTHER) { return successful () - && (success || (set_error (err_type), false)); + && (success || err (err_type)); } template @@ -169,8 +169,7 @@ struct hb_serialize_context_t { if ((long long) v1 != (long long) v2) { - set_error (err_type); - return false; + return err (err_type); } return true; } @@ -213,7 +212,7 @@ struct hb_serialize_context_t { // Offset overflows that occur before link resolution cannot be handled // by repacking, so set a more general error. - if (offset_overflow ()) set_error (HB_SERIALIZE_ERROR_OTHER); + if (offset_overflow ()) err (HB_SERIALIZE_ERROR_OTHER); return; } @@ -394,7 +393,7 @@ struct hb_serialize_context_t for (const object_t::link_t &link : parent->links) { const object_t* child = packed[link.objidx]; - if (unlikely (!child)) { set_error (HB_SERIALIZE_ERROR_OTHER); return; } + if (unlikely (!child)) { err (HB_SERIALIZE_ERROR_OTHER); return; } unsigned offset = 0; switch ((whence_t) link.whence) { case Head: offset = child->head - parent->head; break; @@ -441,9 +440,9 @@ struct hb_serialize_context_t Type *start_embed (const Type &obj) const { return start_embed (hb_addressof (obj)); } - void set_error (hb_serialize_error_t err_type) + bool err (hb_serialize_error_t err_type) { - errors = errors | err_type; + return bool ((errors = (errors | err_type))); } template @@ -453,7 +452,7 @@ struct hb_serialize_context_t if (this->tail - this->head < ptrdiff_t (size)) { - set_error (HB_SERIALIZE_ERROR_OUT_OF_ROOM); + err (HB_SERIALIZE_ERROR_OUT_OF_ROOM); return nullptr; } memset (this->head, 0, size); From 46bf03d6919087e4ce8f0626a3d342380346dc97 Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Thu, 18 Mar 2021 14:35:36 -0700 Subject: [PATCH 30/30] [subset] add NODISCARD to error checking methods on serializer. --- src/hb-serialize.hh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hb-serialize.hh b/src/hb-serialize.hh index a4303fc34..87aafe0e2 100644 --- a/src/hb-serialize.hh +++ b/src/hb-serialize.hh @@ -133,9 +133,9 @@ struct hb_serialize_context_t bool successful () const { return !bool (errors); } - bool ran_out_of_room () const { return errors & HB_SERIALIZE_ERROR_OUT_OF_ROOM; } - bool offset_overflow () const { return errors & HB_SERIALIZE_ERROR_OFFSET_OVERFLOW; } - bool only_offset_overflow () const { return errors == HB_SERIALIZE_ERROR_OFFSET_OVERFLOW; } + HB_NODISCARD bool ran_out_of_room () const { return errors & HB_SERIALIZE_ERROR_OUT_OF_ROOM; } + HB_NODISCARD bool offset_overflow () const { return errors & HB_SERIALIZE_ERROR_OFFSET_OVERFLOW; } + HB_NODISCARD bool only_offset_overflow () const { return errors == HB_SERIALIZE_ERROR_OFFSET_OVERFLOW; } void reset (void *start_, unsigned int size) {