]> git.ipfire.org Git - thirdparty/vectorscan.git/commitdiff
rose_build: reduce size/scope of context objects
authorAlex Coyte <a.coyte@intel.com>
Thu, 30 Mar 2017 03:34:33 +0000 (14:34 +1100)
committerMatthew Barr <matthew.barr@intel.com>
Wed, 26 Apr 2017 05:19:01 +0000 (15:19 +1000)
14 files changed:
src/nfa/castlecompile.cpp
src/rose/rose_build_anchored.cpp
src/rose/rose_build_anchored.h
src/rose/rose_build_bytecode.cpp
src/rose/rose_build_compile.cpp
src/rose/rose_build_dump.cpp
src/rose/rose_build_dump.h
src/rose/rose_build_impl.h
src/rose/rose_build_matchers.cpp
src/rose/rose_build_matchers.h
src/rose/rose_build_program.cpp
src/util/multibit_build.cpp
src/util/multibit_build.h
unit/internal/multi_bit.cpp

index 3b40ab9a81eaaf91ea8c78ce6342f9f57f1191e2..a7fe1e903c54537eeef5dde2d94c654f9ff5d150 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015-2016, Intel Corporation
+ * Copyright (c) 2015-2017, Intel Corporation
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -560,7 +560,7 @@ buildCastle(const CastleProto &proto,
     DEBUG_PRINTF("%zu subcastles may go stale\n", may_stale.size());
     vector<mmbit_sparse_iter> stale_iter;
     if (!may_stale.empty()) {
-        mmbBuildSparseIterator(stale_iter, may_stale, numRepeats);
+        stale_iter = mmbBuildSparseIterator(may_stale, numRepeats);
     }
 
 
index d4e08bb340e556b706725ca14e0f619cfe5d835d..6c7bb1c1e6faea33bcaee2e9596becbdaa6eaf7a 100644 (file)
@@ -30,6 +30,7 @@
 
 #include "grey.h"
 #include "rose_build_impl.h"
+#include "rose_build_matchers.h"
 #include "rose_internal.h"
 #include "ue2common.h"
 #include "nfa/dfa_min.h"
@@ -71,6 +72,8 @@ namespace ue2 {
 
 #define INIT_STATE (DEAD_STATE + 1)
 
+#define NO_FRAG_ID (~0U)
+
 // Adds a vertex with the given reach.
 static
 NFAVertex add_vertex(NGHolder &h, const CharReach &cr) {
@@ -173,7 +176,7 @@ void mergeAnchoredDfas(vector<unique_ptr<raw_dfa>> &dfas,
 }
 
 static
-void remapAnchoredReports(raw_dfa &rdfa, const RoseBuildImpl &build) {
+void remapAnchoredReports(raw_dfa &rdfa, const vector<u32> &frag_map) {
     for (dstate &ds : rdfa.states) {
         assert(ds.reports_eod.empty()); // Not used in anchored matcher.
         if (ds.reports.empty()) {
@@ -182,8 +185,8 @@ void remapAnchoredReports(raw_dfa &rdfa, const RoseBuildImpl &build) {
 
         flat_set<ReportID> new_reports;
         for (auto id : ds.reports) {
-            assert(id < build.literal_info.size());
-            new_reports.insert(build.literal_info.at(id).fragment_id);
+            assert(id < frag_map.size());
+            new_reports.insert(frag_map[id]);
         }
         ds.reports = std::move(new_reports);
     }
@@ -194,13 +197,29 @@ void remapAnchoredReports(raw_dfa &rdfa, const RoseBuildImpl &build) {
  * ids) with the fragment id for each literal.
  */
 static
-void remapAnchoredReports(RoseBuildImpl &build) {
+void remapAnchoredReports(RoseBuildImpl &build, const vector<u32> &frag_map) {
     for (auto &m : build.anchored_nfas) {
         for (auto &rdfa : m.second) {
             assert(rdfa);
-            remapAnchoredReports(*rdfa, build);
+            remapAnchoredReports(*rdfa, frag_map);
+        }
+    }
+}
+
+/**
+ * Returns mapping from literal ids to fragment ids.
+ */
+static
+vector<u32> reverseFragMap(const RoseBuildImpl &build,
+                           const vector<LitFragment> &fragments) {
+    vector<u32> rev(build.literal_info.size(), NO_FRAG_ID);
+    for (const auto &f : fragments) {
+        for (u32 lit_id : f.lit_ids) {
+            assert(lit_id < rev.size());
+            rev[lit_id] = f.fragment_id;
         }
     }
+    return rev;
 }
 
 /**
@@ -208,7 +227,7 @@ void remapAnchoredReports(RoseBuildImpl &build) {
  * raw_dfa with program offsets.
  */
 static
-void remapIdsToPrograms(const RoseBuildImpl &build, raw_dfa &rdfa) {
+void remapIdsToPrograms(const vector<LitFragment> &fragments, raw_dfa &rdfa) {
     for (dstate &ds : rdfa.states) {
         assert(ds.reports_eod.empty()); // Not used in anchored matcher.
         if (ds.reports.empty()) {
@@ -217,7 +236,7 @@ void remapIdsToPrograms(const RoseBuildImpl &build, raw_dfa &rdfa) {
 
         flat_set<ReportID> new_reports;
         for (auto fragment_id : ds.reports) {
-            auto &frag = build.fragments.at(fragment_id);
+            const auto &frag = fragments.at(fragment_id);
             new_reports.insert(frag.lit_program_offset);
         }
         ds.reports = std::move(new_reports);
@@ -731,7 +750,7 @@ int addToAnchoredMatcher(RoseBuildImpl &build, const NGHolder &anchored,
 }
 
 static
-void buildSimpleDfas(const RoseBuildImpl &build,
+void buildSimpleDfas(const RoseBuildImpl &build, const vector<u32> &frag_map,
                      vector<unique_ptr<raw_dfa>> *anchored_dfas) {
     /* we should have determinised all of these before so there should be no
      * chance of failure. */
@@ -739,7 +758,8 @@ void buildSimpleDfas(const RoseBuildImpl &build,
     for (const auto &simple : build.anchored_simple) {
         exit_ids.clear();
         for (auto lit_id : simple.second) {
-            exit_ids.insert(build.literal_info[lit_id].fragment_id);
+            assert(lit_id < frag_map.size());
+            exit_ids.insert(frag_map[lit_id]);
         }
         auto h = populate_holder(simple.first, exit_ids);
         Automaton_Holder autom(*h);
@@ -760,7 +780,8 @@ void buildSimpleDfas(const RoseBuildImpl &build,
  * from RoseBuildImpl.
  */
 static
-vector<unique_ptr<raw_dfa>> getAnchoredDfas(RoseBuildImpl &build) {
+vector<unique_ptr<raw_dfa>> getAnchoredDfas(RoseBuildImpl &build,
+                                            const vector<u32> &frag_map) {
     vector<unique_ptr<raw_dfa>> dfas;
 
     // DFAs that already exist as raw_dfas.
@@ -773,7 +794,7 @@ vector<unique_ptr<raw_dfa>> getAnchoredDfas(RoseBuildImpl &build) {
 
     // DFAs we currently have as simple literals.
     if (!build.anchored_simple.empty()) {
-        buildSimpleDfas(build, &dfas);
+        buildSimpleDfas(build, frag_map, &dfas);
         build.anchored_simple.clear();
     }
 
@@ -825,7 +846,8 @@ size_t buildNfas(vector<raw_dfa> &anchored_dfas,
     return total_size;
 }
 
-vector<raw_dfa> buildAnchoredDfas(RoseBuildImpl &build) {
+vector<raw_dfa> buildAnchoredDfas(RoseBuildImpl &build,
+                                  const vector<LitFragment> &fragments) {
     vector<raw_dfa> dfas;
 
     if (build.anchored_nfas.empty() && build.anchored_simple.empty()) {
@@ -833,9 +855,10 @@ vector<raw_dfa> buildAnchoredDfas(RoseBuildImpl &build) {
         return dfas;
     }
 
-    remapAnchoredReports(build);
+    const auto frag_map = reverseFragMap(build, fragments);
+    remapAnchoredReports(build, frag_map);
 
-    auto anch_dfas = getAnchoredDfas(build);
+    auto anch_dfas = getAnchoredDfas(build, frag_map);
     mergeAnchoredDfas(anch_dfas, build);
 
     dfas.reserve(anch_dfas.size());
@@ -847,8 +870,8 @@ vector<raw_dfa> buildAnchoredDfas(RoseBuildImpl &build) {
 }
 
 aligned_unique_ptr<anchored_matcher_info>
-buildAnchoredMatcher(RoseBuildImpl &build, vector<raw_dfa> &dfas,
-                     size_t *asize) {
+buildAnchoredMatcher(RoseBuildImpl &build, const vector<LitFragment> &fragments,
+                     vector<raw_dfa> &dfas, size_t *asize) {
     const CompileContext &cc = build.cc;
 
     if (dfas.empty()) {
@@ -858,7 +881,7 @@ buildAnchoredMatcher(RoseBuildImpl &build, vector<raw_dfa> &dfas,
     }
 
     for (auto &rdfa : dfas) {
-        remapIdsToPrograms(build, rdfa);
+        remapIdsToPrograms(fragments, rdfa);
     }
 
     vector<aligned_unique_ptr<NFA>> nfas;
index ad89df652df43468198c9eeea258f0a63134ae49..dd59ca32b19e2b061786ac9782d74fe1bb6c424d 100644 (file)
@@ -44,11 +44,13 @@ namespace ue2 {
 
 class RoseBuildImpl;
 struct raw_dfa;
+struct LitFragment;
 
 /**
  * \brief Construct a set of anchored DFAs from our anchored literals/engines.
  */
-std::vector<raw_dfa> buildAnchoredDfas(RoseBuildImpl &build);
+std::vector<raw_dfa> buildAnchoredDfas(RoseBuildImpl &build,
+                                    const std::vector<LitFragment> &fragments);
 
 /**
  * \brief Construct an anchored_matcher_info runtime structure from the given
@@ -58,8 +60,9 @@ std::vector<raw_dfa> buildAnchoredDfas(RoseBuildImpl &build);
  * given in litPrograms.
  */
 aligned_unique_ptr<anchored_matcher_info>
-buildAnchoredMatcher(RoseBuildImpl &build, std::vector<raw_dfa> &dfas,
-                     size_t *asize);
+buildAnchoredMatcher(RoseBuildImpl &build,
+                     const std::vector<LitFragment> &fragments,
+                     std::vector<raw_dfa> &dfas, size_t *asize);
 
 u32 anchoredStateSize(const anchored_matcher_info &atable);
 
index a0edc7118ffcf16dc1734bc18b1afb9702d44bc0..411134574f9afb207d86c9c0b14087e048443c5a 100644 (file)
@@ -187,13 +187,6 @@ struct build_context : noncopyable {
     /** \brief mapping from suffix to queue index. */
     map<suffix_id, u32> suffixes;
 
-    /** \brief Number of roles with a state bit.
-     *
-     * This is set by assignStateIndices() and should be constant throughout
-     * the rest of the compile.
-     */
-    size_t numStates = 0;
-
     /** \brief Simple cache of programs written to engine blob, used for
      * deduplication. */
     ue2::unordered_map<RoseProgram, u32, RoseProgramHash,
@@ -216,8 +209,10 @@ struct build_context : noncopyable {
      * and reach table size. */
     size_t reachTableSize = 0;
 
-    /** \brief State indices, for those roles that have them. */
-    ue2::unordered_map<RoseVertex, u32> roleStateIndices;
+    /** \brief State indices, for those roles that have them.
+     * Each vertex present has a unique state index in the range
+     * [0, roleStateIndices.size()). */
+    unordered_map<RoseVertex, u32> roleStateIndices;
 
     /** \brief Mapping from queue index to bytecode offset for built engines
      * that have already been pushed into the engine_blob. */
@@ -227,9 +222,6 @@ struct build_context : noncopyable {
      * that need hash table support. */
     vector<ue2_case_string> longLiterals;
 
-    /** \brief Minimum offset of a match from the floating table. */
-    u32 floatingMinLiteralMatchOffset = 0;
-
     /** \brief Long literal length threshold, used in streaming mode. */
     size_t longLitLengthThreshold = 0;
 
@@ -251,6 +243,13 @@ struct build_context : noncopyable {
 /** \brief Data only used during construction of various programs (literal,
  * anchored, delay, etc). */
 struct ProgramBuild : noncopyable {
+    explicit ProgramBuild(u32 fMinLitOffset)
+        : floatingMinLiteralMatchOffset(fMinLitOffset) {
+    }
+
+    /** \brief Minimum offset of a match from the floating table. */
+    const u32 floatingMinLiteralMatchOffset;
+
     /** \brief Mapping from vertex to key, for vertices with a
      * CHECK_NOT_HANDLED instruction. */
     ue2::unordered_map<RoseVertex, u32> handledKeys;
@@ -444,21 +443,21 @@ bool isSingleOutfix(const RoseBuildImpl &tbi) {
 }
 
 static
-u8 pickRuntimeImpl(const RoseBuildImpl &build, const build_context &bc,
+u8 pickRuntimeImpl(const RoseBuildImpl &build, const RoseResources &resources,
                    UNUSED u32 outfixEndQueue) {
-    DEBUG_PRINTF("has_outfixes=%d\n", bc.resources.has_outfixes);
-    DEBUG_PRINTF("has_suffixes=%d\n", bc.resources.has_suffixes);
-    DEBUG_PRINTF("has_leftfixes=%d\n", bc.resources.has_leftfixes);
-    DEBUG_PRINTF("has_literals=%d\n", bc.resources.has_literals);
-    DEBUG_PRINTF("has_states=%d\n", bc.resources.has_states);
-    DEBUG_PRINTF("checks_groups=%d\n", bc.resources.checks_groups);
-    DEBUG_PRINTF("has_lit_delay=%d\n", bc.resources.has_lit_delay);
-    DEBUG_PRINTF("has_lit_check=%d\n", bc.resources.has_lit_check);
-    DEBUG_PRINTF("has_anchored=%d\n", bc.resources.has_anchored);
-    DEBUG_PRINTF("has_floating=%d\n", bc.resources.has_floating);
-    DEBUG_PRINTF("has_eod=%d\n", bc.resources.has_eod);
-
-    if (isPureFloating(bc.resources, build.cc)) {
+    DEBUG_PRINTF("has_outfixes=%d\n", resources.has_outfixes);
+    DEBUG_PRINTF("has_suffixes=%d\n", resources.has_suffixes);
+    DEBUG_PRINTF("has_leftfixes=%d\n", resources.has_leftfixes);
+    DEBUG_PRINTF("has_literals=%d\n", resources.has_literals);
+    DEBUG_PRINTF("has_states=%d\n", resources.has_states);
+    DEBUG_PRINTF("checks_groups=%d\n", resources.checks_groups);
+    DEBUG_PRINTF("has_lit_delay=%d\n", resources.has_lit_delay);
+    DEBUG_PRINTF("has_lit_check=%d\n", resources.has_lit_check);
+    DEBUG_PRINTF("has_anchored=%d\n", resources.has_anchored);
+    DEBUG_PRINTF("has_floating=%d\n", resources.has_floating);
+    DEBUG_PRINTF("has_eod=%d\n", resources.has_eod);
+
+    if (isPureFloating(resources, build.cc)) {
         return ROSE_RUNTIME_PURE_LITERAL;
     }
 
@@ -1196,7 +1195,6 @@ bool buildLeftfix(RoseBuildImpl &build, build_context &bc, bool prefix, u32 qi,
 
     setLeftNfaProperties(*nfa, leftfix);
 
-    build.leftfix_queue_map.emplace(leftfix, qi);
     nfa->queueIndex = qi;
 
     if (!prefix && !leftfix.haig() && leftfix.graph()
@@ -1374,9 +1372,9 @@ void buildSuffixContainer(RoseGraph &g, build_context &bc,
 
 static
 void updateExclusiveInfixProperties(const RoseBuildImpl &build,
-                                    build_context &bc,
-                                    const vector<ExclusiveInfo> &exclusive_info,
-                                    set<u32> *no_retrigger_queues) {
+                                const vector<ExclusiveInfo> &exclusive_info,
+                                map<RoseVertex, left_build_info> &leftfix_info,
+                                set<u32> *no_retrigger_queues) {
     const RoseGraph &g = build.g;
     for (const auto &info : exclusive_info) {
         // Set leftfix optimisations, disabled for tamarama subengines
@@ -1426,9 +1424,10 @@ void updateExclusiveInfixProperties(const RoseBuildImpl &build,
             const auto &verts = sub.vertices;
             for (const auto &v : verts) {
                 u32 lag = g[v].left.lag;
-                bc.leftfix_info.emplace(
-                    v, left_build_info(qi, lag, max_width, squash_mask, stop,
-                                       max_queuelen, cm_count, cm_cr));
+                leftfix_info.emplace(v, left_build_info(qi, lag, max_width,
+                                                        squash_mask, stop,
+                                                        max_queuelen, cm_count,
+                                                        cm_cr));
             }
         }
     }
@@ -1490,7 +1489,7 @@ void buildExclusiveInfixes(RoseBuildImpl &build, build_context &bc,
         info.queue = qif.get_queue();
         exclusive_info.push_back(move(info));
     }
-    updateExclusiveInfixProperties(build, bc, exclusive_info,
+    updateExclusiveInfixProperties(build, exclusive_info, bc.leftfix_info, 
                                    no_retrigger_queues);
     buildInfixContainer(g, bc, exclusive_info);
 }
@@ -1843,7 +1842,7 @@ bool prepOutfixes(RoseBuildImpl &tbi, build_context &bc,
 }
 
 static
-void assignSuffixQueues(RoseBuildImpl &build, build_context &bc) {
+void assignSuffixQueues(RoseBuildImpl &build, map<suffix_id, u32> &suffixes) {
     const RoseGraph &g = build.g;
 
     for (auto v : vertices_range(g)) {
@@ -1856,14 +1855,13 @@ void assignSuffixQueues(RoseBuildImpl &build, build_context &bc) {
         DEBUG_PRINTF("vertex %zu triggers suffix %p\n", g[v].index, s.graph());
 
         // We may have already built this NFA.
-        if (contains(bc.suffixes, s)) {
+        if (contains(suffixes, s)) {
             continue;
         }
 
         u32 queue = build.qif.get_queue();
         DEBUG_PRINTF("assigning %p to queue %u\n", s.graph(), queue);
-        bc.suffixes.emplace(s, queue);
-        build.suffix_queue_map.emplace(s, queue);
+        suffixes.emplace(s, queue);
     }
 }
 
@@ -2096,7 +2094,7 @@ bool buildNfas(RoseBuildImpl &tbi, build_context &bc, QueueIndexFactory &qif,
                               no_retrigger_queues);
     }
 
-    assignSuffixQueues(tbi, bc);
+    assignSuffixQueues(tbi, bc.suffixes);
 
     if (!buildSuffixes(tbi, bc, no_retrigger_queues, suffixTriggers)) {
         return false;
@@ -2268,8 +2266,7 @@ u32 buildLastByteIter(const RoseGraph &g, build_context &bc) {
         return 0; /* invalid offset */
     }
 
-    vector<mmbit_sparse_iter> iter;
-    mmbBuildSparseIterator(iter, lb_roles, bc.numStates);
+    auto iter = mmbBuildSparseIterator(lb_roles, bc.roleStateIndices.size());
     return bc.engine_blob.add_iterator(iter);
 }
 
@@ -2382,8 +2379,7 @@ u32 buildEodNfaIterator(build_context &bc, const u32 activeQueueCount) {
 
     DEBUG_PRINTF("building iter for %zu nfas\n", keys.size());
 
-    vector<mmbit_sparse_iter> iter;
-    mmbBuildSparseIterator(iter, keys, activeQueueCount);
+    auto iter = mmbBuildSparseIterator(keys, activeQueueCount);
     return bc.engine_blob.add_iterator(iter);
 }
 
@@ -2512,16 +2508,13 @@ void recordResources(RoseResources &resources, const RoseProgram &program) {
 
 static
 void recordResources(RoseResources &resources,
-                     const RoseBuildImpl &build) {
+                     const RoseBuildImpl &build,
+                     const vector<LitFragment> &fragments) {
     if (!build.outfixes.empty()) {
         resources.has_outfixes = true;
     }
 
-    resources.has_literals =
-        any_of(begin(build.literal_info), end(build.literal_info),
-               [](const rose_literal_info &info) {
-                   return info.fragment_id != MO_INVALID_IDX;
-               });
+    resources.has_literals = !fragments.empty();
 
     const auto &g = build.g;
     for (const auto &v : vertices_range(g)) {
@@ -2537,20 +2530,21 @@ void recordResources(RoseResources &resources,
 }
 
 static
-void recordLongLiterals(build_context &bc, const RoseProgram &program) {
+void recordLongLiterals(vector<ue2_case_string> &longLiterals,
+                        const RoseProgram &program) {
     for (const auto &ri : program) {
         if (const auto *ri_check =
                 dynamic_cast<const RoseInstrCheckLongLit *>(ri.get())) {
             DEBUG_PRINTF("found CHECK_LONG_LIT for string '%s'\n",
                          escapeString(ri_check->literal).c_str());
-            bc.longLiterals.emplace_back(ri_check->literal, false);
+            longLiterals.emplace_back(ri_check->literal, false);
             continue;
         }
         if (const auto *ri_check =
                 dynamic_cast<const RoseInstrCheckLongLitNocase *>(ri.get())) {
             DEBUG_PRINTF("found CHECK_LONG_LIT_NOCASE for string '%s'\n",
                          escapeString(ri_check->literal).c_str());
-            bc.longLiterals.emplace_back(ri_check->literal, true);
+            longLiterals.emplace_back(ri_check->literal, true);
         }
     }
 }
@@ -2569,7 +2563,7 @@ u32 writeProgram(build_context &bc, RoseProgram &&program) {
     }
 
     recordResources(bc.resources, program);
-    recordLongLiterals(bc, program);
+    recordLongLiterals(bc.longLiterals, program);
 
     u32 len = 0;
     auto prog_bytecode = writeProgram(bc.engine_blob, program, &len);
@@ -2581,7 +2575,7 @@ u32 writeProgram(build_context &bc, RoseProgram &&program) {
 }
 
 static
-u32 writeActiveLeftIter(build_context &bc,
+u32 writeActiveLeftIter(RoseEngineBlob &engine_blob,
                         const vector<LeftNfaInfo> &leftInfoTable) {
     vector<u32> keys;
     for (size_t i = 0; i < leftInfoTable.size(); i++) {
@@ -2597,9 +2591,8 @@ u32 writeActiveLeftIter(build_context &bc,
         return 0;
     }
 
-    vector<mmbit_sparse_iter> iter;
-    mmbBuildSparseIterator(iter, keys, verify_u32(leftInfoTable.size()));
-    return bc.engine_blob.add_iterator(iter);
+    auto iter = mmbBuildSparseIterator(keys, verify_u32(leftInfoTable.size()));
+    return engine_blob.add_iterator(iter);
 }
 
 static
@@ -2726,19 +2719,20 @@ void writeLookaroundTables(build_context &bc, RoseEngine &proto) {
 }
 
 static
-void writeDkeyInfo(const ReportManager &rm, build_context &bc,
+void writeDkeyInfo(const ReportManager &rm, RoseEngineBlob &engine_blob,
                    RoseEngine &proto) {
     const auto inv_dkeys = rm.getDkeyToReportTable();
-    proto.invDkeyOffset = bc.engine_blob.add_range(inv_dkeys);
+    proto.invDkeyOffset = engine_blob.add_range(inv_dkeys);
     proto.dkeyCount = rm.numDkeys();
     proto.dkeyLogSize = fatbit_size(proto.dkeyCount);
 }
 
 static
-void writeLeftInfo(build_context &bc, RoseEngine &proto,
+void writeLeftInfo(RoseEngineBlob &engine_blob, RoseEngine &proto,
                    const vector<LeftNfaInfo> &leftInfoTable) {
-    proto.leftOffset = bc.engine_blob.add_range(leftInfoTable);
-    proto.activeLeftIterOffset = writeActiveLeftIter(bc, leftInfoTable);
+    proto.leftOffset = engine_blob.add_range(leftInfoTable);
+    proto.activeLeftIterOffset
+        = writeActiveLeftIter(engine_blob, leftInfoTable);
     proto.roseCount = verify_u32(leftInfoTable.size());
     proto.activeLeftCount = verify_u32(leftInfoTable.size());
     proto.rosePrefixCount = countRosePrefixes(leftInfoTable);
@@ -3592,10 +3586,11 @@ void makeRoleLookaround(const RoseBuildImpl &build, build_context &bc,
 }
 
 static
-void makeRoleCheckLeftfix(const RoseBuildImpl &build, build_context &bc,
+void makeRoleCheckLeftfix(const RoseBuildImpl &build,
+                          const map<RoseVertex, left_build_info> &leftfix_info,
                           RoseVertex v, RoseProgram &program) {
-    auto it = bc.leftfix_info.find(v);
-    if (it == end(bc.leftfix_info)) {
+    auto it = leftfix_info.find(v);
+    if (it == end(leftfix_info)) {
         return;
     }
     const left_build_info &lni = it->second;
@@ -3623,7 +3618,8 @@ void makeRoleCheckLeftfix(const RoseBuildImpl &build, build_context &bc,
 }
 
 static
-void makeRoleAnchoredDelay(const RoseBuildImpl &build, build_context &bc,
+void makeRoleAnchoredDelay(const RoseBuildImpl &build,
+                           u32 floatingMinLiteralMatchOffset,
                            RoseVertex v, RoseProgram &program) {
     // Only relevant for roles that can be triggered by the anchored table.
     if (!build.isAnchored(v)) {
@@ -3632,7 +3628,7 @@ void makeRoleAnchoredDelay(const RoseBuildImpl &build, build_context &bc,
 
     // If this match cannot occur after floatingMinLiteralMatchOffset, we do
     // not need this check.
-    if (build.g[v].max_offset <= bc.floatingMinLiteralMatchOffset) {
+    if (build.g[v].max_offset <= floatingMinLiteralMatchOffset) {
         return;
     }
 
@@ -3662,9 +3658,9 @@ void makeDedupeSom(const RoseBuildImpl &build, const Report &report,
 }
 
 static
-void makeCatchup(const RoseBuildImpl &build, const build_context &bc,
+void makeCatchup(const RoseBuildImpl &build, bool needs_catchup,
                  const flat_set<ReportID> &reports, RoseProgram &program) {
-    if (!bc.needs_catchup) {
+    if (!needs_catchup) {
         return;
     }
 
@@ -3685,9 +3681,9 @@ void makeCatchup(const RoseBuildImpl &build, const build_context &bc,
 }
 
 static
-void makeCatchupMpv(const RoseBuildImpl &build, const build_context &bc,
+void makeCatchupMpv(const RoseBuildImpl &build, bool needs_mpv_catchup,
                     ReportID id, RoseProgram &program) {
-    if (!bc.needs_mpv_catchup) {
+    if (!needs_mpv_catchup) {
         return;
     }
 
@@ -3931,7 +3927,7 @@ void makeRoleReports(const RoseBuildImpl &build, const build_context &bc,
     }
 
     const auto &reports = g[v].reports;
-    makeCatchup(build, bc, reports, program);
+    makeCatchup(build, bc.needs_catchup, reports, program);
 
     RoseProgram report_block;
     for (ReportID id : reports) {
@@ -4067,12 +4063,12 @@ void makeRoleInfixTriggers(const RoseBuildImpl &build, const build_context &bc,
 }
 
 static
-void makeRoleSetState(const build_context &bc, RoseVertex v,
-                      RoseProgram &program) {
+void makeRoleSetState(const unordered_map<RoseVertex, u32> &roleStateIndices,
+                      RoseVertex v, RoseProgram &program) {
     // We only need this instruction if a state index has been assigned to this
     // vertex.
-    auto it = bc.roleStateIndices.find(v);
-    if (it == end(bc.roleStateIndices)) {
+    auto it = roleStateIndices.find(v);
+    if (it == end(roleStateIndices)) {
         return;
     }
     program.add_before_end(make_unique<RoseInstrSetState>(it->second));
@@ -4181,7 +4177,8 @@ RoseProgram makeProgram(const RoseBuildImpl &build, build_context &bc,
     // First, add program instructions that enforce preconditions without
     // effects.
 
-    makeRoleAnchoredDelay(build, bc, v, program);
+    makeRoleAnchoredDelay(build, prog_build.floatingMinLiteralMatchOffset, v,
+                          program);
 
     if (onlyAtEod(build, v)) {
         DEBUG_PRINTF("only at eod\n");
@@ -4201,7 +4198,7 @@ RoseProgram makeProgram(const RoseBuildImpl &build, build_context &bc,
     }
 
     makeRoleLookaround(build, bc, v, program);
-    makeRoleCheckLeftfix(build, bc, v, program);
+    makeRoleCheckLeftfix(build, bc.leftfix_info, v, program);
 
     // Next, we can add program instructions that have effects. This must be
     // done as a series of blocks, as some of them (like reports) are
@@ -4228,7 +4225,7 @@ RoseProgram makeProgram(const RoseBuildImpl &build, build_context &bc,
     effects_block.add_block(move(suffix_block));
 
     RoseProgram state_block;
-    makeRoleSetState(bc, v, state_block);
+    makeRoleSetState(bc.roleStateIndices, v, state_block);
     effects_block.add_block(move(state_block));
 
     // Note: EOD eager reports may generate a CHECK_ONLY_EOD instruction (if
@@ -4279,11 +4276,11 @@ void makeBoundaryPrograms(const RoseBuildImpl &build, build_context &bc,
 }
 
 static
-void assignStateIndices(const RoseBuildImpl &build, build_context &bc) {
+unordered_map<RoseVertex, u32> assignStateIndices(const RoseBuildImpl &build) {
     const auto &g = build.g;
 
     u32 state = 0;
-
+    unordered_map<RoseVertex, u32> roleStateIndices;
     for (auto v : vertices_range(g)) {
         // Virtual vertices (starts, EOD accept vertices) never need state
         // indices.
@@ -4306,12 +4303,13 @@ void assignStateIndices(const RoseBuildImpl &build, build_context &bc) {
         }
 
         /* TODO: also don't need a state index if all edges are nfa based */
-        bc.roleStateIndices.emplace(v, state++);
+        roleStateIndices.emplace(v, state++);
     }
 
     DEBUG_PRINTF("assigned %u states (from %zu vertices)\n", state,
                  num_vertices(g));
-    bc.numStates = state;
+
+    return roleStateIndices;
 }
 
 static
@@ -4426,8 +4424,7 @@ void addPredBlockSingle(u32 pred_state, RoseProgram &pred_block,
 }
 
 static
-void addPredBlocksAny(const build_context &bc,
-                      map<u32, RoseProgram> &pred_blocks,
+void addPredBlocksAny(map<u32, RoseProgram> &pred_blocks, u32 num_states,
                       RoseProgram &program) {
     RoseProgram sparse_program;
 
@@ -4437,7 +4434,7 @@ void addPredBlocksAny(const build_context &bc,
     }
 
     const RoseInstruction *end_inst = sparse_program.end_instruction();
-    auto ri = make_unique<RoseInstrSparseIterAny>(bc.numStates, keys, end_inst);
+    auto ri = make_unique<RoseInstrSparseIterAny>(num_states, keys, end_inst);
     sparse_program.add_before_end(move(ri));
 
     RoseProgram &block = pred_blocks.begin()->second;
@@ -4446,9 +4443,8 @@ void addPredBlocksAny(const build_context &bc,
 }
 
 static
-void addPredBlocksMulti(const build_context &bc,
-                        map<u32, RoseProgram> &pred_blocks,
-                        RoseProgram &program) {
+void addPredBlocksMulti(map<u32, RoseProgram> &pred_blocks,
+                        u32 num_states, RoseProgram &program) {
     assert(!pred_blocks.empty());
 
     RoseProgram sparse_program;
@@ -4456,8 +4452,7 @@ void addPredBlocksMulti(const build_context &bc,
     vector<pair<u32, const RoseInstruction *>> jump_table;
 
     // BEGIN instruction.
-    auto ri_begin =
-        make_unique<RoseInstrSparseIterBegin>(bc.numStates, end_inst);
+    auto ri_begin = make_unique<RoseInstrSparseIterBegin>(num_states, end_inst);
     RoseInstrSparseIterBegin *begin_inst = ri_begin.get();
     sparse_program.add_before_end(move(ri_begin));
 
@@ -4498,7 +4493,7 @@ void addPredBlocksMulti(const build_context &bc,
 }
 
 static
-void addPredBlocks(const build_context &bc, map<u32, RoseProgram> &pred_blocks,
+void addPredBlocks(map<u32, RoseProgram> &pred_blocks, u32 num_states,
                    RoseProgram &program) {
     // Trim empty blocks, if any exist.
     for (auto it = pred_blocks.begin(); it != pred_blocks.end();) {
@@ -4527,11 +4522,11 @@ void addPredBlocks(const build_context &bc, map<u32, RoseProgram> &pred_blocks,
             return RoseProgramEquivalence()(*begin(blocks), block);
         })) {
         DEBUG_PRINTF("all blocks equiv\n");
-        addPredBlocksAny(bc, pred_blocks, program);
+        addPredBlocksAny(pred_blocks, num_states, program);
         return;
     }
 
-    addPredBlocksMulti(bc, pred_blocks, program);
+    addPredBlocksMulti(pred_blocks, num_states, program);
 }
 
 static
@@ -4658,14 +4653,15 @@ u32 findMinOffset(const RoseBuildImpl &build, u32 lit_id) {
 }
 
 static
-void makeCheckLitEarlyInstruction(const RoseBuildImpl &build, build_context &bc,
-                                  u32 lit_id, const vector<RoseEdge> &lit_edges,
+void makeCheckLitEarlyInstruction(const RoseBuildImpl &build, u32 lit_id,
+                                  const vector<RoseEdge> &lit_edges,
+                                  u32 floatingMinLiteralMatchOffset,
                                   RoseProgram &program) {
     if (lit_edges.empty()) {
         return;
     }
 
-    if (bc.floatingMinLiteralMatchOffset == 0) {
+    if (floatingMinLiteralMatchOffset == 0) {
         return;
     }
 
@@ -4677,18 +4673,17 @@ void makeCheckLitEarlyInstruction(const RoseBuildImpl &build, build_context &bc,
     const auto &lit = build.literals.right.at(lit_id);
     size_t min_len = lit.elength();
     u32 min_offset = findMinOffset(build, lit_id);
-    DEBUG_PRINTF("has min_len=%zu, min_offset=%u, "
-                 "global min is %u\n", min_len, min_offset,
-                 bc.floatingMinLiteralMatchOffset);
+    DEBUG_PRINTF("has min_len=%zu, min_offset=%u, global min is %u\n", min_len,
+                 min_offset, floatingMinLiteralMatchOffset);
 
     // If we can't match before the min offset, we don't need the check.
-    if (min_len >= bc.floatingMinLiteralMatchOffset) {
+    if (min_len >= floatingMinLiteralMatchOffset) {
         DEBUG_PRINTF("no need for check, min is %u\n",
-                     bc.floatingMinLiteralMatchOffset);
+                     floatingMinLiteralMatchOffset);
         return;
     }
 
-    assert(min_offset >= bc.floatingMinLiteralMatchOffset);
+    assert(min_offset >= floatingMinLiteralMatchOffset);
     assert(min_offset < UINT32_MAX);
 
     DEBUG_PRINTF("adding lit early check, min_offset=%u\n", min_offset);
@@ -4698,13 +4693,13 @@ void makeCheckLitEarlyInstruction(const RoseBuildImpl &build, build_context &bc,
 }
 
 static
-void makeCheckLiteralInstruction(const RoseBuildImpl &build,
-                                 const build_context &bc, u32 lit_id,
+void makeCheckLiteralInstruction(const RoseBuildImpl &build,  u32 lit_id,
+                                 size_t longLitLengthThreshold,
                                  RoseProgram &program) {
-    assert(bc.longLitLengthThreshold > 0);
+    assert(longLitLengthThreshold > 0);
 
     DEBUG_PRINTF("lit_id=%u, long lit threshold %zu\n", lit_id,
-                 bc.longLitLengthThreshold);
+                 longLitLengthThreshold);
 
     if (build.isDelayed(lit_id)) {
         return;
@@ -4722,7 +4717,7 @@ void makeCheckLiteralInstruction(const RoseBuildImpl &build,
         throw ResourceLimitError();
     }
 
-    if (lit.s.length() <= bc.longLitLengthThreshold) {
+    if (lit.s.length() <= longLitLengthThreshold) {
         DEBUG_PRINTF("is a medium-length literal\n");
         const auto *end_inst = program.end_instruction();
         unique_ptr<RoseInstruction> ri;
@@ -4776,7 +4771,8 @@ RoseProgram buildLitInitialProgram(const RoseBuildImpl &build,
     RoseProgram program;
 
     // Check long literal info.
-    makeCheckLiteralInstruction(build, bc, lit_id, program);
+    makeCheckLiteralInstruction(build, lit_id, bc.longLitLengthThreshold,
+                                program);
 
     // Check lit mask.
     makeCheckLitMaskInstruction(build, bc, lit_id, program);
@@ -4792,7 +4788,9 @@ RoseProgram buildLitInitialProgram(const RoseBuildImpl &build,
     makePushDelayedInstructions(build, prog_build, lit_id, program);
 
     // Add pre-check for early literals in the floating table.
-    makeCheckLitEarlyInstruction(build, bc, lit_id, lit_edges, program);
+    makeCheckLitEarlyInstruction(build, lit_id, lit_edges,
+                                 prog_build.floatingMinLiteralMatchOffset,
+                                 program);
 
     return program;
 }
@@ -4827,7 +4825,7 @@ RoseProgram buildLiteralProgram(const RoseBuildImpl &build, build_context &bc,
 
     // Add blocks to deal with non-root edges (triggered by sparse iterator or
     // mmbit_isset checks).
-    addPredBlocks(bc, pred_blocks, program);
+    addPredBlocks(pred_blocks, bc.roleStateIndices.size(), program);
 
     // Add blocks to handle root roles.
     for (const auto &e : lit_edges) {
@@ -4896,7 +4894,7 @@ RoseProgram assembleProgramBlocks(vector<RoseProgram> &&blocks) {
 
 static
 u32 writeLiteralProgram(const RoseBuildImpl &build, build_context &bc,
-                        ProgramBuild &prog_build, const flat_set<u32> &lit_ids,
+                        ProgramBuild &prog_build, const vector<u32> &lit_ids,
                         const map<u32, vector<RoseEdge>> &lit_edge_map,
                         bool is_anchored_program) {
     assert(!lit_ids.empty());
@@ -4947,7 +4945,7 @@ u32 writeLiteralProgram(const RoseBuildImpl &build, build_context &bc,
 static
 u32 writeDelayRebuildProgram(const RoseBuildImpl &build, build_context &bc,
                              ProgramBuild &prog_build,
-                             const flat_set<u32> &lit_ids) {
+                             const vector<u32> &lit_ids) {
     assert(!lit_ids.empty());
 
     if (!build.cc.streaming) {
@@ -4964,7 +4962,8 @@ u32 writeDelayRebuildProgram(const RoseBuildImpl &build, build_context &bc,
         }
 
         RoseProgram prog;
-        makeCheckLiteralInstruction(build, bc, lit_id, prog);
+        makeCheckLiteralInstruction(build, lit_id, bc.longLitLengthThreshold,
+                                    prog);
         makeCheckLitMaskInstruction(build, bc, lit_id, prog);
         makePushDelayedInstructions(build, prog_build, lit_id, prog);
         blocks.push_back(move(prog));
@@ -5046,7 +5045,8 @@ rose_literal_id getFragment(const rose_literal_id &lit) {
 }
 
 static
-void groupByFragment(RoseBuildImpl &build) {
+vector<LitFragment> groupByFragment(const RoseBuildImpl &build) {
+    vector<LitFragment> fragments;
     u32 frag_id = 0;
 
     struct FragmentInfo {
@@ -5055,9 +5055,6 @@ void groupByFragment(RoseBuildImpl &build) {
     };
 
     map<rose_literal_id, FragmentInfo> frag_info;
-    map<u32, u32> lit_to_frag;
-
-    auto &fragments = build.fragments;
 
     for (const auto &m : build.literals.right) {
         const u32 lit_id = m.first;
@@ -5077,8 +5074,7 @@ void groupByFragment(RoseBuildImpl &build) {
         auto groups = info.group_mask;
 
         if (lit.s.length() < ROSE_SHORT_LITERAL_LEN_MAX) {
-            lit_to_frag.emplace(lit_id, frag_id);
-            fragments.emplace_back(frag_id, groups);
+            fragments.emplace_back(frag_id, groups, lit_id);
             frag_id++;
             continue;
         }
@@ -5090,53 +5086,38 @@ void groupByFragment(RoseBuildImpl &build) {
         fi.groups |= groups;
     }
 
-    for (const auto &m : frag_info) {
-        const auto &fi = m.second;
+    for (auto &m : frag_info) {
+        auto &fi = m.second;
         DEBUG_PRINTF("frag %s -> ids: %s\n", dumpString(m.first.s).c_str(),
                      as_string_list(fi.lit_ids).c_str());
-        fragments.emplace_back(frag_id, fi.groups);
-        for (const auto lit_id : fi.lit_ids) {
-            assert(!contains(lit_to_frag, lit_id));
-            lit_to_frag.emplace(lit_id, frag_id);
-        }
+        sort(fi.lit_ids.begin(), fi.lit_ids.end()); /* to match old behaviour */
+        fragments.emplace_back(frag_id, fi.groups, move(fi.lit_ids));
         frag_id++;
+        assert(frag_id == fragments.size());
     }
 
-    // Write the fragment IDs into the literal_info structures.
-    for (const auto &m : lit_to_frag) {
-        build.literal_info[m.first].fragment_id = m.second;
-    }
+    return fragments;
 }
 
 /**
  * \brief Build the interpreter programs for each literal.
  */
 static
-void buildLiteralPrograms(RoseBuildImpl &build, build_context &bc,
+void buildLiteralPrograms(const RoseBuildImpl &build,
+                          vector<LitFragment> &fragments, build_context &bc,
                           ProgramBuild &prog_build) {
-    // Build a reverse mapping from fragment -> {lit_id, lit_id,...}
-    map<u32, flat_set<u32>> frag_to_lit_map;
-    for (u32 lit_id = 0; lit_id < verify_u32(build.literal_info.size());
-         lit_id++) {
-        const auto &info = build.literal_info[lit_id];
-        if (info.fragment_id == MO_INVALID_IDX) {
-            continue;
-        }
-        frag_to_lit_map[info.fragment_id].insert(lit_id);
-    }
-
-    DEBUG_PRINTF("%zu fragments\n", build.fragments.size());
+    DEBUG_PRINTF("%zu fragments\n", fragments.size());
     auto lit_edge_map = findEdgesByLiteral(build);
 
-    for (auto &frag : build.fragments) {
-        const auto &lit_ids = frag_to_lit_map[frag.fragment_id];
+    for (auto &frag : fragments) {
         DEBUG_PRINTF("frag_id=%u, lit_ids=[%s]\n", frag.fragment_id,
-                     as_string_list(lit_ids).c_str());
+                     as_string_list(frag.lit_ids).c_str());
 
-        frag.lit_program_offset = writeLiteralProgram(
-            build, bc, prog_build, lit_ids, lit_edge_map, false);
-        frag.delay_program_offset =
-            writeDelayRebuildProgram(build, bc, prog_build, lit_ids);
+        frag.lit_program_offset
+            = writeLiteralProgram(build, bc, prog_build, frag.lit_ids,
+                                  lit_edge_map, false);
+        frag.delay_program_offset
+            = writeDelayRebuildProgram(build, bc, prog_build, frag.lit_ids);
     }
 }
 
@@ -5147,39 +5128,40 @@ void buildLiteralPrograms(RoseBuildImpl &build, build_context &bc,
  * programs.
  */
 static
-pair<u32, u32> writeDelayPrograms(const RoseBuildImpl &build, build_context &bc,
+pair<u32, u32> writeDelayPrograms(const RoseBuildImpl &build,
+                                  const vector<LitFragment> &fragments,
+                                  build_context &bc,
                                   ProgramBuild &prog_build) {
     auto lit_edge_map = findEdgesByLiteral(build);
 
     vector<u32> programs; // program offsets indexed by (delayed) lit id
     unordered_map<u32, u32> cache; // program offsets we have already seen
 
-    for (const auto &lit_id : build.literals.right | map_keys) {
-        const auto &info = build.literal_info.at(lit_id);
-
-        if (info.fragment_id == MO_INVALID_IDX) {
-            continue; // Unused literal.
-        }
-
-        for (const auto &delayed_lit_id : info.delayed_ids) {
-            DEBUG_PRINTF("lit id %u delay id %u\n", lit_id, delayed_lit_id);
-            u32 offset = writeLiteralProgram(
-                build, bc, prog_build, {delayed_lit_id}, lit_edge_map, false);
-
-            u32 delay_id;
-            auto it = cache.find(offset);
-            if (it != end(cache)) {
-                delay_id = it->second;
-                DEBUG_PRINTF("reusing delay_id %u for offset %u\n", delay_id,
-                             offset);
-            } else {
-                delay_id = verify_u32(programs.size());
-                programs.push_back(offset);
-                cache.emplace(offset, delay_id);
-                DEBUG_PRINTF("assigned new delay_id %u for offset %u\n",
-                             delay_id, offset);
+    for (const auto &frag : fragments) {
+        for (const u32 lit_id : frag.lit_ids) {
+            const auto &info = build.literal_info.at(lit_id);
+
+            for (const auto &delayed_lit_id : info.delayed_ids) {
+                DEBUG_PRINTF("lit id %u delay id %u\n", lit_id, delayed_lit_id);
+                u32 offset = writeLiteralProgram(build, bc, prog_build,
+                                                 {delayed_lit_id}, lit_edge_map,
+                                                 false);
+
+                u32 delay_id;
+                auto it = cache.find(offset);
+                if (it != end(cache)) {
+                    delay_id = it->second;
+                    DEBUG_PRINTF("reusing delay_id %u for offset %u\n",
+                                 delay_id, offset);
+                } else {
+                    delay_id = verify_u32(programs.size());
+                    programs.push_back(offset);
+                    cache.emplace(offset, delay_id);
+                    DEBUG_PRINTF("assigned new delay_id %u for offset %u\n",
+                                 delay_id, offset);
+                }
+                prog_build.delay_programs.emplace(delayed_lit_id, delay_id);
             }
-            prog_build.delay_programs.emplace(delayed_lit_id, delay_id);
         }
     }
 
@@ -5195,6 +5177,7 @@ pair<u32, u32> writeDelayPrograms(const RoseBuildImpl &build, build_context &bc,
  */
 static
 pair<u32, u32> writeAnchoredPrograms(const RoseBuildImpl &build,
+                                     const vector<LitFragment> &fragments,
                                      build_context &bc,
                                      ProgramBuild &prog_build) {
     auto lit_edge_map = findEdgesByLiteral(build);
@@ -5202,44 +5185,42 @@ pair<u32, u32> writeAnchoredPrograms(const RoseBuildImpl &build,
     vector<u32> programs; // program offsets indexed by anchored id
     unordered_map<u32, u32> cache; // program offsets we have already seen
 
-    for (const auto &m : build.literals.right) {
-        u32 lit_id = m.first;
-        const auto &lit = m.second;
+    for (const auto &frag : fragments) {
+        for (const u32 lit_id : frag.lit_ids) {
+            const auto &lit = build.literals.right.at(lit_id);
 
-        if (lit.table != ROSE_ANCHORED) {
-            continue;
-        }
-
-        if (build.literal_info.at(lit_id).fragment_id == MO_INVALID_IDX) {
-            continue; // Unused literal.
-        }
+            if (lit.table != ROSE_ANCHORED) {
+                continue;
+            }
 
-        // If this anchored literal can never match past
-        // floatingMinLiteralMatchOffset, we will never have to record it.
-        if (findMaxOffset(build, lit_id) <= bc.floatingMinLiteralMatchOffset) {
-            DEBUG_PRINTF("can never match after "
-                         "floatingMinLiteralMatchOffset=%u\n",
-                         bc.floatingMinLiteralMatchOffset);
-            continue;
-        }
+            // If this anchored literal can never match past
+            // floatingMinLiteralMatchOffset, we will never have to record it.
+            if (findMaxOffset(build, lit_id)
+                <= prog_build.floatingMinLiteralMatchOffset) {
+                DEBUG_PRINTF("can never match after "
+                             "floatingMinLiteralMatchOffset=%u\n",
+                             prog_build.floatingMinLiteralMatchOffset);
+                continue;
+            }
 
-        u32 offset = writeLiteralProgram(build, bc, prog_build, {lit_id},
-                                         lit_edge_map, true);
-        DEBUG_PRINTF("lit_id=%u -> anch prog at %u\n", lit_id, offset);
+            u32 offset = writeLiteralProgram(build, bc, prog_build, {lit_id},
+                                             lit_edge_map, true);
+            DEBUG_PRINTF("lit_id=%u -> anch prog at %u\n", lit_id, offset);
 
-        u32 anch_id;
-        auto it = cache.find(offset);
-        if (it != end(cache)) {
-            anch_id = it->second;
-            DEBUG_PRINTF("reusing anch_id %u for offset %u\n", anch_id, offset);
-        } else {
-            anch_id = verify_u32(programs.size());
-            programs.push_back(offset);
-            cache.emplace(offset, anch_id);
-            DEBUG_PRINTF("assigned new anch_id %u for offset %u\n", anch_id,
-                         offset);
+            u32 anch_id;
+            auto it = cache.find(offset);
+            if (it != end(cache)) {
+                anch_id = it->second;
+                DEBUG_PRINTF("reusing anch_id %u for offset %u\n", anch_id, offset);
+            } else {
+                anch_id = verify_u32(programs.size());
+                programs.push_back(offset);
+                cache.emplace(offset, anch_id);
+                DEBUG_PRINTF("assigned new anch_id %u for offset %u\n", anch_id,
+                             offset);
+            }
+            prog_build.anchored_programs.emplace(lit_id, anch_id);
         }
-        prog_build.anchored_programs.emplace(lit_id, anch_id);
     }
 
     DEBUG_PRINTF("%zu anchored programs\n", programs.size());
@@ -5283,7 +5264,7 @@ pair<u32, u32> buildReportPrograms(const RoseBuildImpl &build,
     for (ReportID id : reports) {
         RoseProgram program;
         const bool has_som = false;
-        makeCatchupMpv(build, bc, id, program);
+        makeCatchupMpv(build, bc.needs_mpv_catchup, id, program);
         makeReport(build, id, has_som, program);
         applyFinalSpecialisation(program);
         u32 offset = writeProgram(bc, move(program));
@@ -5300,7 +5281,7 @@ pair<u32, u32> buildReportPrograms(const RoseBuildImpl &build,
 
 static
 RoseProgram makeEodAnchorProgram(const RoseBuildImpl &build,
-                                 const build_context &bc,
+                                 bool needs_catchup,
                                  ProgramBuild &prog_build, const RoseEdge &e,
                                  const bool multiple_preds) {
     const RoseGraph &g = build.g;
@@ -5318,7 +5299,7 @@ RoseProgram makeEodAnchorProgram(const RoseBuildImpl &build,
     }
 
     const auto &reports = g[v].reports;
-    makeCatchup(build, bc, reports, program);
+    makeCatchup(build, needs_catchup, reports, program);
 
     const bool has_som = false;
     RoseProgram report_block;
@@ -5393,11 +5374,12 @@ void addEodAnchorProgram(const RoseBuildImpl &build, const build_context &bc,
             assert(contains(bc.roleStateIndices, u));
             u32 pred_state = bc.roleStateIndices.at(u);
             pred_blocks[pred_state].add_block(
-                makeEodAnchorProgram(build, bc, prog_build, e, multiple_preds));
+                makeEodAnchorProgram(build, bc.needs_catchup, prog_build, e,
+                                     multiple_preds));
         }
     }
 
-    addPredBlocks(bc, pred_blocks, program);
+    addPredBlocks(pred_blocks, bc.roleStateIndices.size(), program);
 }
 
 static
@@ -5588,7 +5570,7 @@ void fillMatcherDistances(const RoseBuildImpl &build, RoseEngine *engine) {
 
 static
 u32 writeEagerQueueIter(const set<u32> &eager, u32 leftfixBeginQueue,
-                        u32 queue_count, build_context &bc) {
+                        u32 queue_count, RoseEngineBlob &engine_blob) {
     if (eager.empty()) {
         return 0;
     }
@@ -5599,9 +5581,8 @@ u32 writeEagerQueueIter(const set<u32> &eager, u32 leftfixBeginQueue,
         vec.push_back(q - leftfixBeginQueue);
     }
 
-    vector<mmbit_sparse_iter> iter;
-    mmbBuildSparseIterator(iter, vec, queue_count - leftfixBeginQueue);
-    return bc.engine_blob.add_iterator(iter);
+    auto iter = mmbBuildSparseIterator(vec, queue_count - leftfixBeginQueue);
+    return engine_blob.add_iterator(iter);
 }
 
 static
@@ -5707,6 +5688,19 @@ size_t calcLongLitThreshold(const RoseBuildImpl &build,
     return longLitLengthThreshold;
 }
 
+static
+map<left_id, u32> makeLeftQueueMap(const RoseGraph &g,
+                         const map<RoseVertex, left_build_info> &leftfix_info) {
+    map<left_id, u32> lqm;
+    for (const auto &e : leftfix_info) {
+        left_id left(g[e.first].left);
+        assert(!contains(lqm, left) || lqm[left] == e.second.queue);
+        lqm[left] = e.second.queue;
+    }
+
+    return lqm;
+}
+
 aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildFinalEngine(u32 minWidth) {
     // We keep all our offsets, counts etc. in a prototype RoseEngine which we
     // will copy into the real one once it is allocated: we can't do this
@@ -5730,16 +5724,16 @@ aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildFinalEngine(u32 minWidth) {
                                                          historyRequired);
     DEBUG_PRINTF("longLitLengthThreshold=%zu\n", longLitLengthThreshold);
 
-    groupByFragment(*this);
+    vector<LitFragment> fragments = groupByFragment(*this);
 
-    auto anchored_dfas = buildAnchoredDfas(*this);
+    auto anchored_dfas = buildAnchoredDfas(*this, fragments);
 
     build_context bc;
-    bc.floatingMinLiteralMatchOffset =
-        findMinFloatingLiteralMatch(*this, anchored_dfas);
+    u32 floatingMinLiteralMatchOffset
+        findMinFloatingLiteralMatch(*this, anchored_dfas);
     bc.longLitLengthThreshold = longLitLengthThreshold;
     bc.needs_catchup = needsCatchup(*this, anchored_dfas);
-    recordResources(bc.resources, *this);
+    recordResources(bc.resources, *this, fragments);
     if (!anchored_dfas.empty()) {
         bc.resources.has_anchored = true;
     }
@@ -5777,7 +5771,12 @@ aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildFinalEngine(u32 minWidth) {
         throw ResourceLimitError();
     }
 
-    assignStateIndices(*this, bc);
+    // Enforce role table resource limit.
+    if (num_vertices(g) > cc.grey.limitRoseRoleCount) {
+        throw ResourceLimitError();
+    }
+
+    bc.roleStateIndices = assignStateIndices(*this);
 
     u32 laggedRoseCount = 0;
     vector<LeftNfaInfo> leftInfoTable;
@@ -5786,17 +5785,17 @@ aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildFinalEngine(u32 minWidth) {
                        &laggedRoseCount, &historyRequired);
 
     // Information only needed for program construction.
-    ProgramBuild prog_build;
+    ProgramBuild prog_build(floatingMinLiteralMatchOffset);
     prog_build.vertex_group_map = getVertexGroupMap(*this);
     prog_build.squashable_groups = getSquashableGroups(*this);
 
     tie(proto.anchoredProgramOffset, proto.anchored_count) =
-        writeAnchoredPrograms(*this, bc, prog_build);
+        writeAnchoredPrograms(*this, fragments, bc, prog_build);
 
     tie(proto.delayProgramOffset, proto.delay_count) =
-        writeDelayPrograms(*this, bc, prog_build);
+        writeDelayPrograms(*this, fragments, bc, prog_build);
 
-    buildLiteralPrograms(*this, bc, prog_build);
+    buildLiteralPrograms(*this, fragments, bc, prog_build);
 
     proto.eodProgramOffset =
         writeEodProgram(*this, bc, prog_build, eodNfaIterOffset);
@@ -5808,22 +5807,17 @@ aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildFinalEngine(u32 minWidth) {
 
     proto.lastByteHistoryIterOffset = buildLastByteIter(g, bc);
     proto.eagerIterOffset = writeEagerQueueIter(
-        eager_queues, proto.leftfixBeginQueue, queue_count, bc);
+        eager_queues, proto.leftfixBeginQueue, queue_count, bc.engine_blob);
 
     addSomRevNfas(bc, proto, ssm);
 
     writeLookaroundTables(bc, proto);
-    writeDkeyInfo(rm, bc, proto);
-    writeLeftInfo(bc, proto, leftInfoTable);
-
-    // Enforce role table resource limit.
-    if (num_vertices(g) > cc.grey.limitRoseRoleCount) {
-        throw ResourceLimitError();
-    }
+    writeDkeyInfo(rm, bc.engine_blob, proto);
+    writeLeftInfo(bc.engine_blob, proto, leftInfoTable);
 
     // Build anchored matcher.
     size_t asize = 0;
-    auto atable = buildAnchoredMatcher(*this, anchored_dfas, &asize);
+    auto atable = buildAnchoredMatcher(*this, fragments, anchored_dfas, &asize);
     if (atable) {
         proto.amatcherOffset = bc.engine_blob.add(atable.get(), asize, 64);
     }
@@ -5831,7 +5825,8 @@ aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildFinalEngine(u32 minWidth) {
     // Build floating HWLM matcher.
     rose_group fgroups = 0;
     size_t fsize = 0;
-    auto ftable = buildFloatingMatcher(*this, bc.longLitLengthThreshold,
+    auto ftable = buildFloatingMatcher(*this, fragments,
+                                       bc.longLitLengthThreshold,
                                        &fgroups, &fsize, &historyRequired);
     if (ftable) {
         proto.fmatcherOffset = bc.engine_blob.add(ftable.get(), fsize, 64);
@@ -5840,22 +5835,22 @@ aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildFinalEngine(u32 minWidth) {
 
     // Build delay rebuild HWLM matcher.
     size_t drsize = 0;
-    auto drtable =
-        buildDelayRebuildMatcher(*this, bc.longLitLengthThreshold, &drsize);
+    auto drtable = buildDelayRebuildMatcher(*this, fragments,
+                                            bc.longLitLengthThreshold, &drsize);
     if (drtable) {
         proto.drmatcherOffset = bc.engine_blob.add(drtable.get(), drsize, 64);
     }
 
     // Build EOD-anchored HWLM matcher.
     size_t esize = 0;
-    auto etable = buildEodAnchoredMatcher(*this, &esize);
+    auto etable = buildEodAnchoredMatcher(*this, fragments, &esize);
     if (etable) {
         proto.ematcherOffset = bc.engine_blob.add(etable.get(), esize, 64);
     }
 
     // Build small-block HWLM matcher.
     size_t sbsize = 0;
-    auto sbtable = buildSmallBlockMatcher(*this, &sbsize);
+    auto sbtable = buildSmallBlockMatcher(*this, fragments, &sbsize);
     if (sbtable) {
         proto.sbmatcherOffset = bc.engine_blob.add(sbtable.get(), sbsize, 64);
     }
@@ -5873,7 +5868,7 @@ aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildFinalEngine(u32 minWidth) {
     assert(!cc.streaming || historyRequired <=
            max(cc.grey.maxHistoryAvailable, cc.grey.somMaxRevNfaLength));
 
-    fillStateOffsets(*this, bc.numStates, proto.anchorStateSize,
+    fillStateOffsets(*this, bc.roleStateIndices.size(), proto.anchorStateSize,
                      proto.activeArrayCount, proto.activeLeftCount,
                      laggedRoseCount, longLitStreamStateRequired,
                      historyRequired, &proto.stateOffsets);
@@ -5883,9 +5878,9 @@ aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildFinalEngine(u32 minWidth) {
     writeNfaInfo(*this, bc, proto, no_retrigger_queues);
 
     scatter_plan_raw state_scatter = buildStateScatterPlan(
-        sizeof(u8), bc.numStates, proto.activeLeftCount, proto.rosePrefixCount,
-        proto.stateOffsets, cc.streaming, proto.activeArrayCount,
-        proto.outfixBeginQueue, proto.outfixEndQueue);
+        sizeof(u8), bc.roleStateIndices.size(), proto.activeLeftCount,
+        proto.rosePrefixCount, proto.stateOffsets, cc.streaming,
+        proto.activeArrayCount, proto.outfixBeginQueue, proto.outfixEndQueue);
 
     u32 currOffset;  /* relative to base of RoseEngine */
     if (!bc.engine_blob.empty()) {
@@ -5910,7 +5905,8 @@ aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildFinalEngine(u32 minWidth) {
 
     proto.needsCatchup = bc.needs_catchup ? 1 : 0;
 
-    proto.runtimeImpl = pickRuntimeImpl(*this, bc, proto.outfixEndQueue);
+    proto.runtimeImpl = pickRuntimeImpl(*this, bc.resources,
+                                        proto.outfixEndQueue);
     proto.mpvTriggeredByLeaf = anyEndfixMpvTriggers(*this);
 
     proto.queueCount = queue_count;
@@ -5918,10 +5914,10 @@ aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildFinalEngine(u32 minWidth) {
     proto.handledKeyCount = prog_build.handledKeys.size();
     proto.handledKeyFatbitSize = fatbit_size(proto.handledKeyCount);
 
-    proto.rolesWithStateCount = bc.numStates;
+    proto.rolesWithStateCount = bc.roleStateIndices.size();
 
     proto.initMpvNfa = mpv_as_outfix ? 0 : MO_INVALID_IDX;
-    proto.stateSize = mmbit_size(bc.numStates);
+    proto.stateSize = mmbit_size(bc.roleStateIndices.size());
 
     proto.delay_fatbit_size = fatbit_size(proto.delay_count);
     proto.anchored_fatbit_size = fatbit_size(proto.anchored_count);
@@ -5938,7 +5934,7 @@ aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildFinalEngine(u32 minWidth) {
     proto.fmatcherMaxBiAnchoredWidth = findMaxBAWidth(*this, ROSE_FLOATING);
     proto.minWidth = hasBoundaryReports(boundary) ? 0 : minWidth;
     proto.minWidthExcludingBoundaries = minWidth;
-    proto.floatingMinLiteralMatchOffset = bc.floatingMinLiteralMatchOffset;
+    proto.floatingMinLiteralMatchOffset = floatingMinLiteralMatchOffset;
 
     proto.maxBiAnchoredWidth = findMaxBAWidth(*this);
     proto.noFloatingRoots = hasNoFloatingRoots();
@@ -5977,7 +5973,8 @@ aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildFinalEngine(u32 minWidth) {
 
     DEBUG_PRINTF("rose done %p\n", engine.get());
 
-    dumpRose(*this, engine.get());
+    dumpRose(*this, fragments, makeLeftQueueMap(g, bc.leftfix_info),
+             bc.suffixes, engine.get());
 
     return engine;
 }
index 00586f65d0378fbdd150e6522f1d5542889c610b..791a68ab6db442d3a22d924f6eed27c892618921 100644 (file)
@@ -1669,7 +1669,7 @@ bool roleOffsetsAreValid(const RoseGraph &g) {
 #endif // NDEBUG
 
 aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildRose(u32 minWidth) {
-    dumpRoseGraph(*this, nullptr, "rose_early.dot");
+    dumpRoseGraph(*this, "rose_early.dot");
 
     // Early check for Rose implementability.
     assert(canImplementGraphs(*this));
@@ -1780,7 +1780,7 @@ aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildRose(u32 minWidth) {
     assert(roleOffsetsAreValid(g));
     assert(historiesAreValid(g));
 
-    dumpRoseGraph(*this, nullptr, "rose_pre_norm.dot");
+    dumpRoseGraph(*this, "rose_pre_norm.dot");
 
     return buildFinalEngine(minWidth);
 }
index 30dccb1a393789672ecbf6d7dbf41135ea29b9ca..a52830b031a47c326b04554b9565c7a870ab6868 100644 (file)
@@ -112,8 +112,11 @@ string rose_off::str(void) const {
 
 class RoseGraphWriter {
 public:
-    RoseGraphWriter(const RoseBuildImpl &b_in, const RoseEngine *t_in) :
-        build(b_in), t(t_in) {
+    RoseGraphWriter(const RoseBuildImpl &b_in, const map<u32, u32> &frag_map_in,
+                    const map<left_id, u32> &lqm_in,
+                    const map<suffix_id, u32> &sqm_in, const RoseEngine *t_in)
+        : frag_map(frag_map_in), leftfix_queue_map(lqm_in),
+          suffix_queue_map(sqm_in), build(b_in), t(t_in) {
         for (const auto &m : build.ghost) {
             ghost.insert(m.second);
         }
@@ -160,8 +163,8 @@ public:
         if (g[v].suffix) {
             suffix_id suff(g[v].suffix);
             os << "\\n" << render_kind(suff) << " (top " << g[v].suffix.top;
-            auto it = build.suffix_queue_map.find(suff);
-            if (it != end(build.suffix_queue_map)) {
+            auto it = suffix_queue_map.find(suff);
+            if (it != end(suffix_queue_map)) {
                 os << ", queue " << it->second;
             }
             os << ")";
@@ -174,8 +177,8 @@ public:
         if (g[v].left) {
             left_id left(g[v].left);
             os << "\\n" << render_kind(left) << " (queue ";
-            auto it = build.leftfix_queue_map.find(left);
-            if (it != end(build.leftfix_queue_map)) {
+            auto it = leftfix_queue_map.find(left);
+            if (it != end(leftfix_queue_map)) {
                 os << it->second;
             } else {
                 os << "??";
@@ -248,8 +251,8 @@ private:
     // Render the literal associated with a vertex.
     void writeLiteral(ostream &os, u32 id) const {
         os << "lit=" << id;
-        if (id < build.literal_info.size()) {
-            os << "/" << build.literal_info[id].fragment_id << " ";
+        if (contains(frag_map, id)) {
+            os << "/" << frag_map.at(id) << " ";
         } else {
             os << "/nofrag ";
         }
@@ -269,13 +272,32 @@ private:
     }
 
     set<RoseVertex> ghost;
+    const map<u32, u32> &frag_map;
+    const map<left_id, u32> &leftfix_queue_map;
+    const map<suffix_id, u32> &suffix_queue_map;
     const RoseBuildImpl &build;
     const RoseEngine *t;
 };
 
 } // namespace
 
+static
+map<u32, u32> makeFragMap(const vector<LitFragment> &fragments) {
+    map<u32, u32> fm;
+    for (const auto &f : fragments) {
+        for (u32 id : f.lit_ids) {
+            fm[id] = f.fragment_id;
+        }
+    }
+
+    return fm;
+}
+
+static
 void dumpRoseGraph(const RoseBuildImpl &build, const RoseEngine *t,
+                   const vector<LitFragment> &fragments,
+                   const map<left_id, u32> &leftfix_queue_map,
+                   const map<suffix_id, u32> &suffix_queue_map,
                    const char *filename) {
     const Grey &grey = build.cc.grey;
 
@@ -293,10 +315,16 @@ void dumpRoseGraph(const RoseBuildImpl &build, const RoseEngine *t,
     DEBUG_PRINTF("dumping graph to %s\n", ss.str().c_str());
     ofstream os(ss.str());
 
-    RoseGraphWriter writer(build, t);
+    auto frag_map = makeFragMap(fragments);
+    RoseGraphWriter writer(build, frag_map, leftfix_queue_map, suffix_queue_map,
+                           t);
     writeGraphviz(os, build.g, writer, get(boost::vertex_index, build.g));
 }
 
+void dumpRoseGraph(const RoseBuildImpl &build, const char *filename) {
+    dumpRoseGraph(build, nullptr, {}, {}, {}, filename);
+}
+
 namespace {
 struct CompareVertexRole {
     explicit CompareVertexRole(const RoseGraph &g_in) : g(g_in) {}
@@ -321,11 +349,14 @@ void lit_graph_info(const RoseBuildImpl &build, const rose_literal_info &li,
 }
 
 static
-void dumpRoseLiterals(const RoseBuildImpl &build, const char *filename) {
+void dumpRoseLiterals(const RoseBuildImpl &build,
+                      const vector<LitFragment> &fragments,
+                      const Grey &grey) {
     const RoseGraph &g = build.g;
+    map<u32, u32> frag_map = makeFragMap(fragments);
 
     DEBUG_PRINTF("dumping literals\n");
-    ofstream os(filename);
+    ofstream os(grey.dumpPath + "rose_literals.txt");
 
     os << "ROSE LITERALS: a total of " << build.literals.right.size()
        << " literals and " << num_vertices(g) << " roles." << endl << endl;
@@ -353,8 +384,11 @@ void dumpRoseLiterals(const RoseBuildImpl &build, const char *filename) {
             break;
         }
 
-        os << " ID " << id << "/" << lit_info.fragment_id << ": \""
-           << escapeString(s.get_string()) << "\""
+        os << " ID " << id;
+        if (contains(frag_map, id)) {
+            os << "/" << frag_map.at(id);
+        }
+        os << ": \"" << escapeString(s.get_string()) << "\""
            << " (len " << s.length() << ",";
         if (s.any_nocase()) {
             os << " nocase,";
@@ -833,7 +867,7 @@ void dumpMultipathShufti(ofstream &os, u32 len, const u8 *lo, const u8 *hi,
            #define PROGRAM_CASE(name)                                                     \
     case ROSE_INSTR_##name: {                                                  \
         os << "  " << std::setw(4) << std::setfill('0') << (pc - pc_base)      \
-           << ": " #name " (" << (int)ROSE_INSTR_##name << ")" << endl;        \
+           << ": " #name "\n";                                                 \
         const auto *ri = (const struct ROSE_STRUCT_##name *)pc;
 
 #define PROGRAM_NEXT_INSTRUCTION                                               \
@@ -1444,13 +1478,13 @@ void dumpProgram(ofstream &os, const RoseEngine *t, const char *pc) {
 #undef PROGRAM_NEXT_INSTRUCTION
 
 static
-void dumpRoseLitPrograms(const RoseBuildImpl &build, const RoseEngine *t,
-                         const string &filename) {
+void dumpRoseLitPrograms(const vector<LitFragment> &fragments,
+                         const RoseEngine *t, const string &filename) {
     ofstream os(filename);
 
     // Collect all programs referenced by a literal fragment.
     vector<u32> programs;
-    for (const auto &frag : build.fragments) {
+    for (const auto &frag : fragments) {
         if (frag.lit_program_offset) {
             programs.push_back(frag.lit_program_offset);
         }
@@ -2185,18 +2219,21 @@ void roseDumpComponents(const RoseEngine *t, bool dump_raw,
 }
 
 static
-void roseDumpPrograms(const RoseBuildImpl &build, const RoseEngine *t,
+void roseDumpPrograms(const vector<LitFragment> &fragments, const RoseEngine *t,
                       const string &base) {
-    dumpRoseLitPrograms(build, t, base + "/rose_lit_programs.txt");
+    dumpRoseLitPrograms(fragments, t, base + "/rose_lit_programs.txt");
     dumpRoseEodPrograms(t, base + "/rose_eod_programs.txt");
     dumpRoseReportPrograms(t, base + "/rose_report_programs.txt");
     dumpRoseAnchoredPrograms(t, base + "/rose_anchored_programs.txt");
     dumpRoseDelayPrograms(t, base + "/rose_delay_programs.txt");
 }
 
-void dumpRose(const RoseBuildImpl &build, const RoseEngine *t) {
+void dumpRose(const RoseBuildImpl &build, const vector<LitFragment> &fragments,
+              const map<left_id, u32> &leftfix_queue_map,
+              const map<suffix_id, u32> &suffix_queue_map,
+              const RoseEngine *t) {
     const Grey &grey = build.cc.grey;
-    
+
     if (!grey.dumpFlags) {
         return;
     }
@@ -2218,16 +2255,14 @@ void dumpRose(const RoseBuildImpl &build, const RoseEngine *t) {
     fclose(f);
 
     roseDumpComponents(t, false, grey.dumpPath);
-    roseDumpPrograms(build, t, grey.dumpPath);
+    roseDumpPrograms(fragments, t, grey.dumpPath);
 
     // Graph.
-    dumpRoseGraph(build, t, "rose.dot");
+    dumpRoseGraph(build, t, fragments, leftfix_queue_map, suffix_queue_map,
+                  "rose.dot");
 
-    // Literals.
-    ss.str("");
-    ss.clear();
-    ss << grey.dumpPath << "rose_literals.txt";
-    dumpRoseLiterals(build, ss.str().c_str());
+    // Literals
+    dumpRoseLiterals(build, fragments, grey);
 
     f = fopen((grey.dumpPath + "/rose_struct.txt").c_str(), "w");
     roseDumpStructRaw(t, f);
index 601f5914c4642563eb9880f8bbbe7ae696ec76e3..d4c620a3e62e98d6f898822031eb5afe36d36409 100644 (file)
@@ -29,6 +29,9 @@
 #ifndef ROSE_BUILD_DUMP_H
 #define ROSE_BUILD_DUMP_H
 
+#include "ue2common.h"
+
+#include <map>
 #include <string>
 #include <vector>
 
@@ -39,30 +42,40 @@ namespace ue2 {
 class RoseBuildImpl;
 struct Grey;
 struct hwlmLiteral;
+struct LitFragment;
+struct left_id;
+struct suffix_id;
 
 #ifdef DUMP_SUPPORT
 // Dump the Rose graph in graphviz representation.
-void dumpRoseGraph(const RoseBuildImpl &build, const RoseEngine *t,
-                   const char *filename);
+void dumpRoseGraph(const RoseBuildImpl &build, const char *filename);
 
-void dumpRose(const RoseBuildImpl &build, const RoseEngine *t);
+void dumpRose(const RoseBuildImpl &build,
+              const std::vector<LitFragment> &fragments,
+              const std::map<left_id, u32> &leftfix_queue_map,
+              const std::map<suffix_id, u32> &suffix_queue_map,
+              const RoseEngine *t);
 
 void dumpMatcherLiterals(const std::vector<hwlmLiteral> &lits,
                          const std::string &name, const Grey &grey);
+
 #else
 
 static UNUSED
-void dumpRoseGraph(const RoseBuildImpl &, const RoseEngine *, const char *) {
+void dumpRoseGraph(const RoseBuildImpl &, const char *) {
 }
 
 static UNUSED
-void dumpRose(const RoseBuildImpl &, const RoseEngine *) {
+void dumpRose(const RoseBuildImpl &, const std::vector<LitFragment> &,
+              const std::map<left_id, u32> &, const std::map<suffix_id, u32> &,
+              const RoseEngine *) {
 }
 
 static UNUSED
 void dumpMatcherLiterals(const std::vector<hwlmLiteral> &, const std::string &,
                          const Grey &) {
 }
+
 #endif
 
 } // namespace ue2
index cafd05058ca301484b99ebbd06911b4333fd1d05..b4821b2b380d8fa7abf9195f93de0a38114c87e7 100644 (file)
@@ -264,7 +264,6 @@ struct rose_literal_info {
     ue2::flat_set<RoseVertex> vertices;
     rose_group group_mask = 0;
     u32 undelayed_id = MO_INVALID_IDX;
-    u32 fragment_id = MO_INVALID_IDX; //!< ID corresponding to literal prog.
     bool squash_group = false;
     bool requires_benefits = false;
 };
@@ -437,15 +436,6 @@ private:
 
 std::set<ReportID> all_reports(const OutfixInfo &outfix);
 
-struct LitFragment {
-    LitFragment(u32 fragment_id_in, rose_group groups_in)
-        : fragment_id(fragment_id_in), groups(groups_in) {}
-    u32 fragment_id;
-    rose_group groups;
-    u32 lit_program_offset = ROSE_INVALID_PROG_OFFSET;
-    u32 delay_program_offset = ROSE_INVALID_PROG_OFFSET;
-};
-
 // Concrete impl class
 class RoseBuildImpl : public RoseBuild {
 public:
@@ -576,19 +566,11 @@ public:
 
     u32 ematcher_region_size; /**< number of bytes the eod table runs over */
 
-    /** \brief Mapping from leftfix to queue ID (used in dump code). */
-    unordered_map<left_id, u32> leftfix_queue_map;
-
-    /** \brief Mapping from suffix to queue ID (used in dump code). */
-    unordered_map<suffix_id, u32> suffix_queue_map;
-
     /** \brief Mapping from anchored literal ID to the original literal suffix
      * present when the literal was added to the literal matcher. Used for
      * overlap calculation in history assignment. */
     std::map<u32, rose_literal_id> anchoredLitSuffix;
 
-    std::vector<LitFragment> fragments;
-
     unordered_set<left_id> transient;
     unordered_map<left_id, rose_group> rose_squash_masks;
 
index 5625437bb83f1a15dc7fe20f566da9f826289cbb..7f1467d742bb40b800423dcd3133e9f8855e33a9 100644 (file)
@@ -350,9 +350,6 @@ void findMoreLiteralMasks(RoseBuildImpl &build) {
         const u32 id = e.first;
         const auto &lit = e.second;
 
-        // This pass takes place before fragment IDs are assigned to literals.
-        assert(build.literal_info.at(id).fragment_id == MO_INVALID_IDX);
-
         if (lit.delay || build.isDelayed(id)) {
             continue;
         }
@@ -673,6 +670,7 @@ struct MatcherProto {
  */
 static
 MatcherProto makeMatcherProto(const RoseBuildImpl &build,
+                              const vector<LitFragment> &fragments,
                               rose_literal_table table, bool delay_rebuild,
                               size_t max_len, u32 max_offset = ROSE_BOUND_INF) {
     MatcherProto mp;
@@ -682,92 +680,91 @@ MatcherProto makeMatcherProto(const RoseBuildImpl &build,
         assert(build.cc.streaming);
     }
 
-    for (const auto &e : build.literals.right) {
-        const u32 id = e.first;
-        if (build.literal_info.at(id).fragment_id == MO_INVALID_IDX) {
-            continue;
-        }
+    for (const auto &f : fragments) {
+        for (u32 id : f.lit_ids) {
+            const rose_literal_id &lit = build.literals.right.at(id);
 
-        if (e.second.delay) {
-            continue; /* delay id's are virtual-ish */
-        }
+            if (lit.table != table) {
+                continue; /* wrong table */
+            }
 
-        if (e.second.table != table) {
-            continue; /* wrong table */
-        }
+            if (lit.delay) {
+                continue;  /* delay id's are virtual-ish */
+            }
 
-        assert(id < build.literal_info.size());
-        const rose_literal_info &info = build.literal_info[id];
-        /* Note: requires_benefits are handled in the literal entries */
-        const ue2_literal &lit = e.second.s;
+            assert(id < build.literal_info.size());
+            const auto &info = build.literal_info.at(id);
 
-        DEBUG_PRINTF("lit='%s' (len %zu)\n", escapeString(lit).c_str(),
-                     lit.length());
+            /* Note: requires_benefits are handled in the literal entries */
+            const ue2_literal &s = lit.s;
 
-        // When building the delay rebuild table, we only want to include
-        // literals that have delayed variants.
-        if (delay_rebuild && info.delayed_ids.empty()) {
-            DEBUG_PRINTF("not needed for delay rebuild\n");
-            continue;
-        }
+            DEBUG_PRINTF("lit='%s' (len %zu)\n", escapeString(s).c_str(),
+                         s.length());
 
-        if (max_offset != ROSE_BOUND_INF) {
-            u64a min_report = literalMinReportOffset(build, e.second, info);
-            if (min_report > max_offset) {
-                DEBUG_PRINTF("min report offset=%llu exceeds max_offset=%u\n",
-                             min_report, max_offset);
+            // When building the delay rebuild table, we only want to include
+            // literals that have delayed variants.
+            if (delay_rebuild && info.delayed_ids.empty()) {
+                DEBUG_PRINTF("not needed for delay rebuild\n");
                 continue;
             }
-        }
 
-        const vector<u8> &msk = e.second.msk;
-        const vector<u8> &cmp = e.second.cmp;
-        bool noruns = isNoRunsLiteral(build, id, info, max_len);
+            if (max_offset != ROSE_BOUND_INF) {
+                u64a min_report = literalMinReportOffset(build, lit, info);
+                if (min_report > max_offset) {
+                    DEBUG_PRINTF("min report offset=%llu exceeds "
+                                 "max_offset=%u\n", min_report, max_offset);
+                    continue;
+                }
+            }
 
-        size_t lit_hist_len = 0;
-        if (build.cc.streaming) {
-            lit_hist_len = max(msk.size(), min(lit.length(), max_len));
-            lit_hist_len = lit_hist_len ? lit_hist_len - 1 : 0;
-        }
-        DEBUG_PRINTF("lit requires %zu bytes of history\n", lit_hist_len);
-        assert(lit_hist_len <= build.cc.grey.maxHistoryAvailable);
-
-        auto lit_final = lit; // copy
-
-        if (lit_final.length() > ROSE_SHORT_LITERAL_LEN_MAX) {
-            DEBUG_PRINTF("truncating to tail of length %zu\n",
-                         size_t{ROSE_SHORT_LITERAL_LEN_MAX});
-            lit_final.erase(0, lit_final.length() - ROSE_SHORT_LITERAL_LEN_MAX);
-            // We shouldn't have set a threshold below 8 chars.
-            assert(msk.size() <= ROSE_SHORT_LITERAL_LEN_MAX);
-            assert(!noruns);
-        }
+            const vector<u8> &msk = lit.msk;
+            const vector<u8> &cmp = lit.cmp;
+            bool noruns = isNoRunsLiteral(build, id, info, max_len);
 
-        const auto &s = lit_final.get_string();
-        bool nocase = lit_final.any_nocase();
+            size_t lit_hist_len = 0;
+            if (build.cc.streaming) {
+                lit_hist_len = max(msk.size(), min(s.length(), max_len));
+                lit_hist_len = lit_hist_len ? lit_hist_len - 1 : 0;
+            }
+            DEBUG_PRINTF("lit requires %zu bytes of history\n", lit_hist_len);
+            assert(lit_hist_len <= build.cc.grey.maxHistoryAvailable);
+
+            auto lit_final = s; // copy
+
+            if (lit_final.length() > ROSE_SHORT_LITERAL_LEN_MAX) {
+                DEBUG_PRINTF("truncating to tail of length %zu\n",
+                             size_t{ROSE_SHORT_LITERAL_LEN_MAX});
+                lit_final.erase(0, lit_final.length()
+                                - ROSE_SHORT_LITERAL_LEN_MAX);
+                // We shouldn't have set a threshold below 8 chars.
+                assert(msk.size() <= ROSE_SHORT_LITERAL_LEN_MAX);
+                assert(!noruns);
+            }
 
-        DEBUG_PRINTF("id=%u, s='%s', nocase=%d, noruns=%d, msk=%s, "
-                     "cmp=%s\n",
-                     info.fragment_id, escapeString(s).c_str(), (int)nocase,
-                     noruns, dumpMask(msk).c_str(), dumpMask(cmp).c_str());
+            const auto &s_final = lit_final.get_string();
+            bool nocase = lit_final.any_nocase();
 
-        if (!maskIsConsistent(s, nocase, msk, cmp)) {
-            DEBUG_PRINTF("msk/cmp for literal can't match, skipping\n");
-            continue;
-        }
+            DEBUG_PRINTF("id=%u, s='%s', nocase=%d, noruns=%d, msk=%s, "
+                         "cmp=%s\n", f.fragment_id,
+                         escapeString(s_final).c_str(), (int)nocase, noruns,
+                         dumpMask(msk).c_str(), dumpMask(cmp).c_str());
 
-        mp.accel_lits.emplace_back(lit.get_string(), lit.any_nocase(), msk, cmp,
-                                   info.group_mask);
-        mp.history_required = max(mp.history_required, lit_hist_len);
+            if (!maskIsConsistent(s_final, nocase, msk, cmp)) {
+                DEBUG_PRINTF("msk/cmp for literal can't match, skipping\n");
+                continue;
+            }
+
+            mp.accel_lits.emplace_back(s.get_string(), s.any_nocase(), msk, cmp,
+                                       info.group_mask);
+            mp.history_required = max(mp.history_required, lit_hist_len);
 
-        assert(info.fragment_id < build.fragments.size());
-        const auto &frag = build.fragments.at(info.fragment_id);
-        u32 prog_offset =
-            delay_rebuild ? frag.delay_program_offset : frag.lit_program_offset;
-        const auto &groups = frag.groups;
+            u32 prog_offset = delay_rebuild ? f.delay_program_offset
+                                            : f.lit_program_offset;
+            const auto &groups = f.groups;
 
-        mp.lits.emplace_back(move(s), nocase, noruns, prog_offset, groups, msk,
-                             cmp);
+            mp.lits.emplace_back(move(s_final), nocase, noruns, prog_offset,
+                                 groups, msk, cmp);
+        }
     }
 
     sort_and_unique(mp.lits);
@@ -809,14 +806,15 @@ void buildAccel(const RoseBuildImpl &build, const MatcherProto &mp,
 }
 
 aligned_unique_ptr<HWLM>
-buildFloatingMatcher(const RoseBuildImpl &build, size_t longLitLengthThreshold,
-                     rose_group *fgroups, size_t *fsize,
-                     size_t *historyRequired) {
+buildFloatingMatcher(const RoseBuildImpl &build,
+                     const vector<LitFragment> &fragments,
+                     size_t longLitLengthThreshold, rose_group *fgroups,
+                     size_t *fsize, size_t *historyRequired) {
     *fsize = 0;
     *fgroups = 0;
 
-    auto mp =
-        makeMatcherProto(build, ROSE_FLOATING, false, longLitLengthThreshold);
+    auto mp = makeMatcherProto(build, fragments, ROSE_FLOATING, false,
+                               longLitLengthThreshold);
     if (mp.lits.empty()) {
         DEBUG_PRINTF("empty floating matcher\n");
         return nullptr;
@@ -847,6 +845,7 @@ buildFloatingMatcher(const RoseBuildImpl &build, size_t longLitLengthThreshold,
 }
 
 aligned_unique_ptr<HWLM> buildDelayRebuildMatcher(const RoseBuildImpl &build,
+                                       const vector<LitFragment> &fragments,
                                                   size_t longLitLengthThreshold,
                                                   size_t *drsize) {
     *drsize = 0;
@@ -856,8 +855,8 @@ aligned_unique_ptr<HWLM> buildDelayRebuildMatcher(const RoseBuildImpl &build,
         return nullptr;
     }
 
-    auto mp =
-        makeMatcherProto(build, ROSE_FLOATING, true, longLitLengthThreshold);
+    auto mp = makeMatcherProto(build, fragments, ROSE_FLOATING, true,
+                               longLitLengthThreshold);
     if (mp.lits.empty()) {
         DEBUG_PRINTF("empty delay rebuild matcher\n");
         return nullptr;
@@ -877,8 +876,9 @@ aligned_unique_ptr<HWLM> buildDelayRebuildMatcher(const RoseBuildImpl &build,
     return hwlm;
 }
 
-aligned_unique_ptr<HWLM> buildSmallBlockMatcher(const RoseBuildImpl &build,
-                                                size_t *sbsize) {
+aligned_unique_ptr<HWLM>
+buildSmallBlockMatcher(const RoseBuildImpl &build,
+                       const vector<LitFragment> &fragments, size_t *sbsize) {
     *sbsize = 0;
 
     if (build.cc.streaming) {
@@ -893,7 +893,7 @@ aligned_unique_ptr<HWLM> buildSmallBlockMatcher(const RoseBuildImpl &build,
         return nullptr;
     }
 
-    auto mp = makeMatcherProto(build, ROSE_FLOATING, false,
+    auto mp = makeMatcherProto(build, fragments, ROSE_FLOATING, false,
                                ROSE_SMALL_BLOCK_LEN, ROSE_SMALL_BLOCK_LEN);
     if (mp.lits.empty()) {
         DEBUG_PRINTF("no floating table\n");
@@ -903,9 +903,10 @@ aligned_unique_ptr<HWLM> buildSmallBlockMatcher(const RoseBuildImpl &build,
         return nullptr;
     }
 
-    auto mp_anchored =
-        makeMatcherProto(build, ROSE_ANCHORED_SMALL_BLOCK, false,
-                         ROSE_SMALL_BLOCK_LEN, ROSE_SMALL_BLOCK_LEN);
+    auto mp_anchored = makeMatcherProto(build, fragments,
+                                        ROSE_ANCHORED_SMALL_BLOCK, false,
+                                        ROSE_SMALL_BLOCK_LEN,
+                                        ROSE_SMALL_BLOCK_LEN);
     if (mp_anchored.lits.empty()) {
         DEBUG_PRINTF("no small-block anchored literals\n");
         return nullptr;
@@ -937,11 +938,12 @@ aligned_unique_ptr<HWLM> buildSmallBlockMatcher(const RoseBuildImpl &build,
     return hwlm;
 }
 
-aligned_unique_ptr<HWLM> buildEodAnchoredMatcher(const RoseBuildImpl &build,
-                                                 size_t *esize) {
+aligned_unique_ptr<HWLM>
+buildEodAnchoredMatcher(const RoseBuildImpl &build,
+                        const vector<LitFragment> &fragments, size_t *esize) {
     *esize = 0;
 
-    auto mp = makeMatcherProto(build, ROSE_EOD_ANCHORED, false,
+    auto mp = makeMatcherProto(build, fragments, ROSE_EOD_ANCHORED, false,
                                build.ematcher_region_size);
 
     if (mp.lits.empty()) {
index cb56037ddbda3e180011c5c7a936052a05ede9a2..494a3aeb9740a859fc75e183252d2957c5a640cb 100644 (file)
 
 #include "rose_build_impl.h"
 
+#include <vector>
+
 struct Grey;
 struct HWLM;
 
 namespace ue2 {
 
+struct LitFragment {
+    LitFragment(u32 fragment_id_in, rose_group groups_in, u32 lit_id)
+    : fragment_id(fragment_id_in), groups(groups_in), lit_ids({lit_id}) {}
+    LitFragment(u32 fragment_id_in, rose_group groups_in,
+                std::vector<u32> lit_ids_in)
+    : fragment_id(fragment_id_in), groups(groups_in),
+        lit_ids(std::move(lit_ids_in)) {}
+    u32 fragment_id;
+    rose_group groups;
+    std::vector<u32> lit_ids;
+    u32 lit_program_offset = ROSE_INVALID_PROG_OFFSET;
+    u32 delay_program_offset = ROSE_INVALID_PROG_OFFSET;
+};
+
 aligned_unique_ptr<HWLM> buildFloatingMatcher(const RoseBuildImpl &build,
+                                  const std::vector<LitFragment> &fragments,
                                               size_t longLitLengthThreshold,
                                               rose_group *fgroups,
                                               size_t *fsize,
                                               size_t *historyRequired);
 
 aligned_unique_ptr<HWLM> buildDelayRebuildMatcher(const RoseBuildImpl &build,
+                                  const std::vector<LitFragment> &fragments,
                                                   size_t longLitLengthThreshold,
                                                   size_t *drsize);
 
 aligned_unique_ptr<HWLM> buildSmallBlockMatcher(const RoseBuildImpl &build,
+                                  const std::vector<LitFragment> &fragments,
                                                 size_t *sbsize);
 
 aligned_unique_ptr<HWLM> buildEodAnchoredMatcher(const RoseBuildImpl &build,
+                                  const std::vector<LitFragment> &fragments,
                                                  size_t *esize);
 
 void findMoreLiteralMasks(RoseBuildImpl &build);
index 9e030e8efd0db696c3cc8b083f2be0749e108218..2fb76c7760ccf61d44ce84fdab16e61f0e1b3e0a 100644 (file)
@@ -450,8 +450,7 @@ void RoseInstrSparseIterBegin::write(void *dest, RoseEngineBlob &blob,
         jump_offsets.push_back(offset_map.at(jump.second));
     }
 
-    vector<mmbit_sparse_iter> iter;
-    mmbBuildSparseIterator(iter, keys, num_keys);
+    auto iter = mmbBuildSparseIterator(keys, num_keys);
     assert(!iter.empty());
     inst->iter_offset = blob.add_iterator(iter);
     inst->jump_table = blob.add(jump_offsets.begin(), jump_offsets.end());
@@ -485,8 +484,7 @@ void RoseInstrSparseIterAny::write(void *dest, RoseEngineBlob &blob,
     inst->fail_jump = calc_jump(offset_map, this, target);
 
     // Write the multibit sparse iterator.
-    vector<mmbit_sparse_iter> iter;
-    mmbBuildSparseIterator(iter, keys, num_keys);
+    auto iter = mmbBuildSparseIterator(keys, num_keys);
     assert(!iter.empty());
     inst->iter_offset = blob.add_iterator(iter);
 }
index c726bdf944547e5c78959170dd0a88adbdafe1d1..ad6a0d6a69b541b5732b55a6a758392538bdebeb 100644 (file)
@@ -155,9 +155,9 @@ void bfs(vector<mmbit_sparse_iter> &out, const TreeNode &tree) {
 
 /** \brief Construct a sparse iterator over the values in \a bits for a
  * multibit of size \a total_bits. */
-void mmbBuildSparseIterator(vector<mmbit_sparse_iter> &out,
-                            const vector<u32> &bits, u32 total_bits) {
-    assert(out.empty());
+vector<mmbit_sparse_iter> mmbBuildSparseIterator(const vector<u32> &bits,
+                                                 u32 total_bits) {
+    vector<mmbit_sparse_iter> out;
     assert(!bits.empty());
     assert(total_bits > 0);
     assert(total_bits <= MMB_MAX_BITS);
@@ -186,6 +186,7 @@ void mmbBuildSparseIterator(vector<mmbit_sparse_iter> &out,
 #endif
 
     DEBUG_PRINTF("iter has %zu records\n", out.size());
+    return out;
 }
 
 template<typename T>
index 951f1fb46a95b8503fe2ed515e0cc459b498c900..2d7b5fc26877813e89c8d80b1f2733f0011a139c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015-2016, Intel Corporation
+ * Copyright (c) 2015-2017, Intel Corporation
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -61,8 +61,8 @@ u32 mmbit_size(u32 total_bits);
 
 /** \brief Construct a sparse iterator over the values in \a bits for a
  * multibit of size \a total_bits. */
-void mmbBuildSparseIterator(std::vector<mmbit_sparse_iter> &out,
-                            const std::vector<u32> &bits, u32 total_bits);
+std::vector<mmbit_sparse_iter>
+mmbBuildSparseIterator(const std::vector<u32> &bits, u32 total_bits);
 
 struct scatter_plan_raw;
 
index 30dce493f6873a1aafdc2a82ce3c254ec34a9cb3..2b0c7c7976b8cd32773c0a69c85afabfef8f44eb 100644 (file)
@@ -782,7 +782,6 @@ TEST_P(MultiBitTest, InitRangePlanChunked) {
 
 TEST(MultiBit, SparseIteratorBegin1) {
     const u32 test_size = 100;
-    vector<mmbit_sparse_iter> it;
     vector<u32> bits;
 
     bits.push_back(1);
@@ -791,7 +790,7 @@ TEST(MultiBit, SparseIteratorBegin1) {
     bits.push_back(35);
     bits.push_back(68);
 
-    mmbBuildSparseIterator(it, bits, test_size);
+    auto it = mmbBuildSparseIterator(bits, test_size);
     //ASSERT_EQ(4U, it.size());
 
     // Trivial initial test: all bits in 'bits' are on, all others are off
@@ -820,7 +819,6 @@ TEST(MultiBit, SparseIteratorBegin1) {
 
 TEST(MultiBit, SparseIteratorBegin2) {
     const u32 test_size = 40000;
-    vector<mmbit_sparse_iter> it;
     vector<u32> bits;
 
     bits.push_back(1);
@@ -830,7 +828,7 @@ TEST(MultiBit, SparseIteratorBegin2) {
     bits.push_back(8920);
     bits.push_back(37000);
 
-    mmbBuildSparseIterator(it, bits, test_size);
+    auto it = mmbBuildSparseIterator(bits, test_size);
     //ASSERT_EQ(12U, it.size());
 
     // Trivial initial test: all bits in 'bits' are on, all others are off
@@ -859,7 +857,6 @@ TEST(MultiBit, SparseIteratorBegin2) {
 
 TEST(MultiBit, SparseIteratorNext1) {
     const u32 test_size = 100;
-    vector<mmbit_sparse_iter> it;
     vector<u32> bits;
 
     bits.push_back(1);
@@ -868,7 +865,7 @@ TEST(MultiBit, SparseIteratorNext1) {
     bits.push_back(35);
     bits.push_back(68);
 
-    mmbBuildSparseIterator(it, bits, test_size);
+    auto it = mmbBuildSparseIterator(bits, test_size);
 
     // Trivial initial test: all bits in 'bits' are on, all others are off
     mmbit_holder ba(test_size);
@@ -924,7 +921,6 @@ TEST(MultiBit, SparseIteratorNext1) {
 
 TEST(MultiBit, SparseIteratorNext2) {
     const u32 test_size = 40000;
-    vector<mmbit_sparse_iter> it;
     vector<u32> bits;
 
     bits.push_back(1);
@@ -939,7 +935,7 @@ TEST(MultiBit, SparseIteratorNext2) {
     bits.push_back(37000);
     bits.push_back(39999);
 
-    mmbBuildSparseIterator(it, bits, test_size);
+    auto it = mmbBuildSparseIterator(bits, test_size);
 
     // Trivial initial test: all bits in 'bits' are on, all others are off
     mmbit_holder ba(test_size);
@@ -995,7 +991,6 @@ TEST(MultiBit, SparseIteratorNext2) {
 
 TEST(MultiBit, SparseIteratorNextSmall) {
     const u32 test_size = 15;
-    vector<mmbit_sparse_iter> it;
     vector<u32> bits;
 
     bits.push_back(1);
@@ -1005,7 +1000,7 @@ TEST(MultiBit, SparseIteratorNextSmall) {
     bits.push_back(12);
     bits.push_back(14);
 
-    mmbBuildSparseIterator(it, bits, test_size);
+    auto it = mmbBuildSparseIterator(bits, test_size);
 
     // Trivial initial test: all bits in 'bits' are on, all others are off
     mmbit_holder ba(test_size);
@@ -1064,13 +1059,12 @@ TEST_P(MultiBitTest, SparseIteratorBeginAll) {
     ASSERT_TRUE(ba != nullptr);
 
     // Put all our bits into the sparse iterator.
-    vector<mmbit_sparse_iter> it;
     vector<u32> bits;
     bits.reserve(test_size / stride);
     for (u64a i = 0; i < test_size; i += stride) {
         bits.push_back(i);
     }
-    mmbBuildSparseIterator(it, bits, test_size);
+    auto it = mmbBuildSparseIterator(bits, test_size);
 
     // Switch all bits on in state.
     mmbit_clear(ba, test_size);
@@ -1104,12 +1098,11 @@ TEST_P(MultiBitTest, SparseIteratorBeginThirds) {
     }
 
     // Put all our bits into the sparse iterator
-    vector<mmbit_sparse_iter> it;
     vector<u32> bits(test_size);
     for (u32 i = 0; i != test_size; i++) {
         bits[i] = i;
     }
-    mmbBuildSparseIterator(it, bits, test_size);
+    auto it = mmbBuildSparseIterator(bits, test_size);
 
     // Switch every third bits on in state
     mmbit_clear(ba, test_size);
@@ -1139,13 +1132,12 @@ TEST_P(MultiBitTest, SparseIteratorNextAll) {
     ASSERT_TRUE(ba != nullptr);
 
     // Put all our bits into the sparse iterator.
-    vector<mmbit_sparse_iter> it;
     vector<u32> bits;
     bits.reserve(test_size / stride);
     for (u64a i = 0; i < test_size; i += stride) {
         bits.push_back(i);
     }
-    mmbBuildSparseIterator(it, bits, test_size);
+    auto it = mmbBuildSparseIterator(bits, test_size);
 
     // Switch all bits on in state
     mmbit_clear(ba, test_size);
@@ -1182,14 +1174,13 @@ TEST_P(MultiBitTest, SparseIteratorNextExactStrided) {
     // Put all our bits into the sparse iterator and switch them on in the
     // state.
     mmbit_clear(ba, test_size);
-    vector<mmbit_sparse_iter> it;
     vector<u32> bits;
     bits.reserve(test_size / stride);
     for (u64a i = 0; i < test_size; i += stride) {
         bits.push_back(i);
         mmbit_set(ba, test_size, i);
     }
-    mmbBuildSparseIterator(it, bits, test_size);
+    auto it = mmbBuildSparseIterator(bits, test_size);
 
     // Iterate over all bits.
     vector<mmbit_sparse_state> state(mmbit_sparse_iter_state_size(test_size));
@@ -1214,13 +1205,12 @@ TEST_P(MultiBitTest, SparseIteratorNextNone) {
     ASSERT_TRUE(ba != nullptr);
 
     // Put all our bits into the sparse iterator.
-    vector<mmbit_sparse_iter> it;
     vector<u32> bits;
     bits.reserve(test_size / stride);
     for (u64a i = 0; i < test_size; i += stride) {
         bits.push_back(i);
     }
-    mmbBuildSparseIterator(it, bits, test_size);
+    auto it = mmbBuildSparseIterator(bits, test_size);
 
     // Switch only the first bit on
     mmbit_clear(ba, test_size);
@@ -1243,13 +1233,12 @@ TEST_P(MultiBitTest, SparseIteratorUnsetAll) {
     ASSERT_TRUE(ba != nullptr);
 
     // Put all our bits into the sparse iterator
-    vector<mmbit_sparse_iter> it;
     vector<u32> bits;
     bits.reserve(test_size / stride);
     for (u64a i = 0; i < test_size; i += stride) {
         bits.push_back(i);
     }
-    mmbBuildSparseIterator(it, bits, test_size);
+    auto it = mmbBuildSparseIterator(bits, test_size);
 
     // Switch all bits on
     mmbit_clear(ba, test_size);
@@ -1283,9 +1272,8 @@ TEST_P(MultiBitTest, SparseIteratorUnsetHalves) {
         odd.push_back(i);
     }
 
-    vector<mmbit_sparse_iter> it_even, it_odd;
-    mmbBuildSparseIterator(it_even, even, test_size);
-    mmbBuildSparseIterator(it_odd, odd, test_size);
+    auto it_even = mmbBuildSparseIterator(even, test_size);
+    auto it_odd = mmbBuildSparseIterator(odd, test_size);
 
     // Switch all bits on
     mmbit_clear(ba, test_size);