]> git.ipfire.org Git - thirdparty/vectorscan.git/commitdiff
Create combo tops for trigger limexes
authorAlex Coyte <a.coyte@intel.com>
Wed, 28 Sep 2016 03:57:24 +0000 (13:57 +1000)
committerMatthew Barr <matthew.barr@intel.com>
Fri, 2 Dec 2016 00:22:23 +0000 (11:22 +1100)
16 files changed:
src/nfa/castlecompile.cpp
src/nfa/limex_compile.cpp
src/nfa/limex_compile.h
src/nfa/limex_limits.h
src/nfagraph/ng_haig.cpp
src/nfagraph/ng_limex.cpp
src/nfagraph/ng_mcclellan.cpp
src/nfagraph/ng_mcclellan_internal.h
src/nfagraph/ng_restructuring.cpp
src/nfagraph/ng_restructuring.h
src/nfagraph/ng_uncalc_components.cpp
src/nfagraph/ng_uncalc_components.h
src/nfagraph/ng_util.cpp
src/nfagraph/ng_util.h
src/rose/rose_build_compile.cpp
src/rose/rose_build_merge.cpp

index 11ae2000a814c65ee5855551292003a1643760c1..b76078f9b2a5e083bd22ff33f95d59029a96ad3d 100644 (file)
@@ -978,11 +978,6 @@ unique_ptr<NGHolder> makeHolder(const CastleProto &proto,
     auto g = ue2::make_unique<NGHolder>(proto.kind);
 
     for (const auto &m : proto.repeats) {
-        if (m.first >= NFA_MAX_TOP_MASKS) {
-            DEBUG_PRINTF("top %u too big for an NFA\n", m.first);
-            return nullptr;
-        }
-
         addToHolder(*g, m.first, m.second);
     }
 
index 53a003e398df48001ed15cdfd771e38e7a8b6e89..2c16409044c434a5dbbb3372121cc714db11a47f 100644 (file)
@@ -41,7 +41,6 @@
 #include "nfagraph/ng_holder.h"
 #include "nfagraph/ng_limex_accel.h"
 #include "nfagraph/ng_repeat.h"
-#include "nfagraph/ng_restructuring.h"
 #include "nfagraph/ng_squash.h"
 #include "nfagraph/ng_util.h"
 #include "ue2common.h"
@@ -74,6 +73,12 @@ using boost::adaptors::map_values;
 
 namespace ue2 {
 
+/**
+ * \brief Special state index value meaning that the vertex will not
+ * participate in an (NFA/DFA/etc) implementation.
+ */
+static constexpr u32 NO_STATE = ~0;
+
 namespace {
 
 struct precalcAccel {
@@ -91,7 +96,7 @@ struct precalcAccel {
 struct limex_accel_info {
     ue2::unordered_set<NFAVertex> accelerable;
     map<NFAStateSet, precalcAccel> precalc;
-    ue2::unordered_map<NFAVertex, flat_set<NFAVertex> > friends;
+    ue2::unordered_map<NFAVertex, flat_set<NFAVertex>> friends;
     ue2::unordered_map<NFAVertex, AccelScheme> accel_map;
 };
 
@@ -134,7 +139,7 @@ struct build_info {
                const vector<BoundedRepeatData> &ri,
                const map<NFAVertex, NFAStateSet> &rsmi,
                const map<NFAVertex, NFAStateSet> &smi,
-               const map<u32, NFAVertex> &ti, const set<NFAVertex> &zi,
+               const map<u32, set<NFAVertex>> &ti, const set<NFAVertex> &zi,
                bool dai, bool sci, const CompileContext &cci,
                u32 nsi)
         : h(hi), state_ids(states_in), repeats(ri), tops(ti), zombies(zi),
@@ -160,7 +165,7 @@ struct build_info {
     map<NFAVertex, NFAStateSet> reportSquashMap;
     map<NFAVertex, NFAStateSet> squashMap;
 
-    const map<u32, NFAVertex> &tops;
+    const map<u32, set<NFAVertex>> &tops;
     ue2::unordered_set<NFAVertex> tugs;
     map<NFAVertex, BoundedRepeatSummary> br_cyclic;
     const set<NFAVertex> &zombies;
@@ -522,20 +527,25 @@ struct fas_visitor : public boost::default_bfs_visitor {
 };
 
 static
-void filterAccelStates(NGHolder &g, const map<u32, NFAVertex> &tops,
+void filterAccelStates(NGHolder &g, const map<u32, set<NFAVertex>> &tops,
                        ue2::unordered_map<NFAVertex, AccelScheme> *accel_map) {
     /* We want the NFA_MAX_ACCEL_STATES best acceleration states, everything
      * else should be ditched. We use a simple BFS to choose accel states near
      * the start. */
 
-    // Temporarily wire start to each top for the BFS.
-    vector<NFAEdge> topEdges;
-    wireStartToTops(g, tops, topEdges);
+    vector<NFAEdge> tempEdges;
+    for (const auto &vv : tops | map_values) {
+        for (NFAVertex v : vv) {
+            if (!edge(g.start, v, g).second) {
+                tempEdges.push_back(add_edge(g.start, v, g).first);
+            }
+        }
+    }
 
     // Similarly, connect (start, startDs) if necessary.
     if (!edge(g.start, g.startDs, g).second) {
         auto e = add_edge(g.start, g.startDs, g).first;
-        topEdges.push_back(e); // Remove edge later.
+        tempEdges.push_back(e); // Remove edge later.
     }
 
     ue2::unordered_map<NFAVertex, AccelScheme> out;
@@ -551,7 +561,7 @@ void filterAccelStates(NGHolder &g, const map<u32, NFAVertex> &tops,
         ; /* found max accel_states */
     }
 
-    remove_edges(topEdges, g);
+    remove_edges(tempEdges, g);
 
     assert(out.size() <= NFA_MAX_ACCEL_STATES);
     accel_map->swap(out);
@@ -705,7 +715,7 @@ void fillAccelInfo(build_info &bi) {
 
 /** The AccelAux structure has large alignment specified, and this makes some
  * compilers do odd things unless we specify a custom allocator. */
-typedef vector<AccelAux, AlignedAllocator<AccelAux, alignof(AccelAux)> >
+typedef vector<AccelAux, AlignedAllocator<AccelAux, alignof(AccelAux)>>
     AccelAuxVector;
 
 #define IMPOSSIBLE_ACCEL_MASK (~0U)
@@ -1122,19 +1132,20 @@ void buildTopMasks(const build_info &args, vector<NFAStateSet> &topMasks) {
 
     u32 numMasks = args.tops.rbegin()->first + 1; // max mask index
     DEBUG_PRINTF("we have %u top masks\n", numMasks);
-    assert(numMasks <= NFA_MAX_TOP_MASKS);
 
     topMasks.assign(numMasks, NFAStateSet(args.num_states)); // all zeroes
 
     for (const auto &m : args.tops) {
         u32 mask_idx = m.first;
-        u32 state_id = args.state_ids.at(m.second);
-        DEBUG_PRINTF("state %u is in top mask %u\n", state_id, mask_idx);
+        for (NFAVertex v : m.second) {
+            u32 state_id = args.state_ids.at(v);
+            DEBUG_PRINTF("state %u is in top mask %u\n", state_id, mask_idx);
 
-        assert(mask_idx < numMasks);
-        assert(state_id != NO_STATE);
+            assert(mask_idx < numMasks);
+            assert(state_id != NO_STATE);
 
-        topMasks[mask_idx].set(state_id);
+            topMasks[mask_idx].set(state_id);
+        }
     }
 }
 
@@ -2123,7 +2134,7 @@ struct Factory {
         u32 maxShift = findMaxVarShift(args, shiftCount);
         findExceptionalTransitions(args, exceptional, maxShift);
 
-        map<ExceptionProto, vector<u32> > exceptionMap;
+        map<ExceptionProto, vector<u32>> exceptionMap;
         vector<ReportID> reportList;
 
         u32 exceptionCount = buildExceptionMap(args, reports_cache, exceptional,
@@ -2315,13 +2326,13 @@ MAKE_LIMEX_TRAITS(512)
 #ifndef NDEBUG
 // Some sanity tests, called by an assertion in generate().
 static UNUSED
-bool isSane(const NGHolder &h, const map<u32, NFAVertex> &tops,
+bool isSane(const NGHolder &h, const map<u32, set<NFAVertex>> &tops,
             const ue2::unordered_map<NFAVertex, u32> &state_ids,
             u32 num_states) {
     ue2::unordered_set<u32> seen;
     ue2::unordered_set<NFAVertex> top_starts;
-    for (const auto &m : tops) {
-        top_starts.insert(m.second);
+    for (const auto &vv : tops | map_values) {
+        insert(&top_starts, vv);
     }
 
     for (auto v : vertices_range(h)) {
@@ -2385,7 +2396,7 @@ aligned_unique_ptr<NFA> generate(NGHolder &h,
                          const vector<BoundedRepeatData> &repeats,
                          const map<NFAVertex, NFAStateSet> &reportSquashMap,
                          const map<NFAVertex, NFAStateSet> &squashMap,
-                         const map<u32, NFAVertex> &tops,
+                         const map<u32, set<NFAVertex>> &tops,
                          const set<NFAVertex> &zombies,
                          bool do_accel,
                          bool stateCompression,
@@ -2457,7 +2468,7 @@ u32 countAccelStates(NGHolder &h,
                      const vector<BoundedRepeatData> &repeats,
                      const map<NFAVertex, NFAStateSet> &reportSquashMap,
                      const map<NFAVertex, NFAStateSet> &squashMap,
-                     const map<u32, NFAVertex> &tops,
+                     const map<u32, set<NFAVertex>> &tops,
                      const set<NFAVertex> &zombies,
                      const CompileContext &cc) {
     const u32 num_states = max_state(states) + 1;
index 62a07e10a8b3b6529bc5a8dd180f07d8dc158a50..21cb76087066f00f104825977df59eedbf70b32b 100644 (file)
@@ -71,7 +71,7 @@ aligned_unique_ptr<NFA> generate(NGHolder &g,
                         const std::vector<BoundedRepeatData> &repeats,
                         const std::map<NFAVertex, NFAStateSet> &reportSquashMap,
                         const std::map<NFAVertex, NFAStateSet> &squashMap,
-                        const std::map<u32, NFAVertex> &tops,
+                        const std::map<u32, std::set<NFAVertex>> &tops,
                         const std::set<NFAVertex> &zombies,
                         bool do_accel,
                         bool stateCompression,
@@ -89,7 +89,7 @@ u32 countAccelStates(NGHolder &h,
                      const std::vector<BoundedRepeatData> &repeats,
                      const std::map<NFAVertex, NFAStateSet> &reportSquashMap,
                      const std::map<NFAVertex, NFAStateSet> &squashMap,
-                     const std::map<u32, NFAVertex> &tops,
+                     const std::map<u32, std::set<NFAVertex>> &tops,
                      const std::set<NFAVertex> &zombies,
                      const CompileContext &cc);
 
index 9b35b115852a14dd0ee42c43819f5539fc141724..f4df54a4b0cb317a7fe50e2f443d4a58c64eb6a2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Intel Corporation
+ * Copyright (c) 2015-2016, Intel Corporation
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -31,6 +31,5 @@
 
 #define NFA_MAX_STATES       512 /**< max states in an NFA */
 #define NFA_MAX_ACCEL_STATES   8 /**< max accel states in a NFA */
-#define NFA_MAX_TOP_MASKS     32 /**< max number of MQE_TOP_N event types */
 
 #endif
index e70b77085f7b6129e86b5f0664073a8c1f90234b..143dca16b79c280f50c5c03155e31f0a08d12945 100644 (file)
@@ -35,7 +35,6 @@
 #include "nfa/goughcompile.h"
 #include "ng_holder.h"
 #include "ng_mcclellan_internal.h"
-#include "ng_restructuring.h"
 #include "ng_som_util.h"
 #include "ng_squash.h"
 #include "ng_util.h"
@@ -118,11 +117,11 @@ public:
     using StateMap = typename Automaton_Traits::StateMap;
 
 protected:
-    Automaton_Base(const NGHolder &graph_in,
-                   const flat_set<NFAVertex> &unused_in, som_type som,
+    Automaton_Base(const NGHolder &graph_in, som_type som,
                    const vector<vector<CharReach>> &triggers,
                    bool unordered_som)
-        : graph(graph_in), numStates(num_vertices(graph)), unused(unused_in),
+        : graph(graph_in), numStates(num_vertices(graph)),
+          unused(getRedundantStarts(graph_in)),
           init(Automaton_Traits::init_states(numStates)),
           initDS(Automaton_Traits::init_states(numStates)),
           squash(Automaton_Traits::init_states(numStates)),
@@ -210,7 +209,7 @@ public:
 
     const NGHolder &graph;
     const u32 numStates;
-    const flat_set<NFAVertex> &unused;
+    const flat_set<NFAVertex> unused;
 
     array<u16, ALPHABET_SIZE> alpha;
     array<u16, ALPHABET_SIZE> unalpha;
@@ -251,10 +250,9 @@ struct Big_Traits {
 
 class Automaton_Big : public Automaton_Base<Big_Traits> {
 public:
-    Automaton_Big(const NGHolder &graph_in,
-                  const flat_set<NFAVertex> &unused_in, som_type som,
+    Automaton_Big(const NGHolder &graph_in, som_type som,
                   const vector<vector<CharReach>> &triggers, bool unordered_som)
-        : Automaton_Base(graph_in, unused_in, som, triggers, unordered_som) {}
+        : Automaton_Base(graph_in, som, triggers, unordered_som) {}
 };
 
 struct Graph_Traits {
@@ -278,11 +276,10 @@ struct Graph_Traits {
 
 class Automaton_Graph : public Automaton_Base<Graph_Traits> {
 public:
-    Automaton_Graph(const NGHolder &graph_in,
-                    const flat_set<NFAVertex> &unused_in, som_type som,
+    Automaton_Graph(const NGHolder &graph_in, som_type som,
                     const vector<vector<CharReach>> &triggers,
                     bool unordered_som)
-        : Automaton_Base(graph_in, unused_in, som, triggers, unordered_som) {}
+        : Automaton_Base(graph_in, som, triggers, unordered_som) {}
 };
 
 class Automaton_Haig_Merge {
@@ -512,15 +509,14 @@ void haig_note_starts(const NGHolder &g, map<u32, u32> *out) {
 
 template<class Auto>
 static
-bool doHaig(const NGHolder &g,
-            const flat_set<NFAVertex> &unused,
-            som_type som, const vector<vector<CharReach>> &triggers,
-            bool unordered_som, raw_som_dfa *rdfa) {
+bool doHaig(const NGHolder &g, som_type som,
+            const vector<vector<CharReach>> &triggers, bool unordered_som,
+            raw_som_dfa *rdfa) {
     u32 state_limit = HAIG_FINAL_DFA_STATE_LIMIT; /* haig never backs down from
                                                      a fight */
     typedef typename Auto::StateSet StateSet;
     vector<StateSet> nfa_state_map;
-    Auto n(g, unused, som, triggers, unordered_som);
+    Auto n(g, som, triggers, unordered_som);
     try {
         if (determinise(n, rdfa->states, state_limit, &nfa_state_map)) {
             DEBUG_PRINTF("state limit exceeded\n");
@@ -550,9 +546,9 @@ bool doHaig(const NGHolder &g,
         haig_do_preds(g, source_states, n.v_by_index,
                       rdfa->state_som.back().preds);
 
-        haig_do_report(g, unused, g.accept, source_states, n.v_by_index,
+        haig_do_report(g, n.unused, g.accept, source_states, n.v_by_index,
                        rdfa->state_som.back().reports);
-        haig_do_report(g, unused, g.acceptEod, source_states, n.v_by_index,
+        haig_do_report(g, n.unused, g.acceptEod, source_states, n.v_by_index,
                        rdfa->state_som.back().reports_eod);
     }
 
@@ -577,8 +573,6 @@ attemptToBuildHaig(const NGHolder &g, som_type som, u32 somPrecision,
     assert(allMatchStatesHaveReports(g));
     assert(hasCorrectlyNumberedVertices(g));
 
-    auto unused = findUnusedStates(g);
-
     u32 numStates = num_vertices(g);
     if (numStates > HAIG_MAX_NFA_STATE) {
         DEBUG_PRINTF("giving up... looks too big\n");
@@ -592,12 +586,11 @@ attemptToBuildHaig(const NGHolder &g, som_type som, u32 somPrecision,
     bool rv;
     if (numStates <= NFA_STATE_LIMIT) {
         /* fast path */
-        rv = doHaig<Automaton_Graph>(g, unused, som, triggers, unordered_som,
+        rv = doHaig<Automaton_Graph>(g, som, triggers, unordered_som,
                                      rdfa.get());
     } else {
         /* not the fast path */
-        rv = doHaig<Automaton_Big>(g, unused, som, triggers, unordered_som,
-                                   rdfa.get());
+        rv = doHaig<Automaton_Big>(g, som, triggers, unordered_som, rdfa.get());
     }
 
     if (!rv) {
index c6e4c24e378116e30a1a00ef300424797849bd5f..66494c77773b3faf20fbb5442f29b2dc47d148fe 100644 (file)
 #include "util/ue2_containers.h"
 #include "util/verify_types.h"
 
+#include <algorithm>
 #include <map>
 #include <vector>
 
+#include <boost/range/adaptor/map.hpp>
+
 using namespace std;
+using boost::adaptors::map_values;
+using boost::adaptors::map_keys;
 
 namespace ue2 {
 
@@ -146,78 +151,310 @@ void dropRedundantStartEdges(NGHolder &g) {
 }
 
 static
-void makeTopStates(NGHolder &g, map<u32, NFAVertex> &tops,
-                   const map<u32, CharReach> &top_reach) {
-    /* TODO: more intelligent creation of top states */
-    map<u32, vector<NFAVertex>> top_succs;
-    for (const auto &e : out_edges_range(g.start, g)) {
-        NFAVertex v = target(e, g);
-        if (v == g.startDs) {
+CharReach calcTopVertexReach(const flat_set<u32> &tops,
+                             const map<u32, CharReach> &top_reach) {
+    CharReach top_cr;
+    for (u32 t : tops) {
+        if (contains(top_reach, t)) {
+            top_cr |= top_reach.at(t);
+        } else {
+            top_cr = CharReach::dot();
+            break;
+        }
+    }
+    return top_cr;
+}
+
+static
+NFAVertex makeTopStartVertex(NGHolder &g, const flat_set<u32> &tops,
+                             const flat_set<NFAVertex> &succs,
+                             const map<u32, CharReach> &top_reach) {
+    assert(!succs.empty());
+    assert(!tops.empty());
+
+    bool reporter = false;
+
+    NFAVertex u = add_vertex(g[g.start], g);
+    CharReach top_cr = calcTopVertexReach(tops, top_reach);
+    g[u].char_reach = top_cr;
+    for (auto v : succs) {
+        if (v == g.accept || v == g.acceptEod) {
+            reporter = true;
+        }
+        add_edge(u, v, g);
+    }
+
+    // Only retain reports (which we copied on add_vertex above) for new top
+    // vertices connected to accepts.
+    if (!reporter) {
+        g[u].reports.clear();
+    }
+
+    return u;
+}
+
+static
+void pickNextTopStateToHandle(const map<u32, flat_set<NFAVertex>> &top_succs,
+                              const map<NFAVertex, flat_set<u32>> &succ_tops,
+                              flat_set<u32> *picked_tops,
+                              flat_set<NFAVertex> *picked_succs) {
+    /* pick top or vertex we want to handle */
+    if (top_succs.size() < succ_tops.size()) {
+        auto best = top_succs.end();
+        for (auto it = top_succs.begin(); it != top_succs.end(); ++it) {
+            if (best == top_succs.end()
+                || it->second.size() < best->second.size()) {
+                best = it;
+            }
+        }
+        assert(best != top_succs.end());
+        assert(!best->second.empty()); /* should already been pruned */
+
+        *picked_tops = { best->first };
+        *picked_succs = best->second;
+    } else {
+        auto best = succ_tops.end();
+        for (auto it = succ_tops.begin(); it != succ_tops.end(); ++it) {
+            /* have to worry about determinism for this one */
+            if (best == succ_tops.end()
+                || it->second.size() < best->second.size()
+                || (it->second.size() == best->second.size()
+                    && it->second < best->second)) {
+                best = it;
+            }
+        }
+        assert(best != succ_tops.end());
+        assert(!best->second.empty()); /* should already been pruned */
+
+        *picked_succs = { best->first };
+        *picked_tops = best->second;
+    }
+}
+
+static
+void expandCbsByTops(const map<u32, flat_set<NFAVertex>> &unhandled_top_succs,
+                     const map<u32, flat_set<NFAVertex>> &top_succs,
+                     const map<NFAVertex, flat_set<u32>> &succ_tops,
+                     flat_set<u32> &picked_tops,
+                     flat_set<NFAVertex> &picked_succs) {
+    NFAVertex v = *picked_succs.begin(); /* arbitrary successor - all equiv */
+    const auto &cand_tops = succ_tops.at(v);
+
+    for (u32 t : cand_tops) {
+        if (!contains(unhandled_top_succs, t)) {
             continue;
         }
-        for (u32 t : g[e].tops) {
-            top_succs[t].push_back(v);
+        if (!has_intersection(unhandled_top_succs.at(t), picked_succs)) {
+            continue; /* not adding any useful work that hasn't already been
+                       * done */
+        }
+        if (!is_subset_of(picked_succs, top_succs.at(t))) {
+            continue; /* will not form a cbs */
         }
+        picked_tops.insert(t);
     }
+}
 
-    for (const auto &top : top_succs) {
-        u32 t = top.first;
+static
+void expandCbsBySuccs(const map<NFAVertex, flat_set<u32>> &unhandled_succ_tops,
+                      const map<u32, flat_set<NFAVertex>> &top_succs,
+                      const map<NFAVertex, flat_set<u32>> &succ_tops,
+                      flat_set<u32> &picked_tops,
+                      flat_set<NFAVertex> &picked_succs) {
+    u32 t = *picked_tops.begin(); /* arbitrary top - all equiv */
+    const auto &cand_succs = top_succs.at(t);
+
+    for (NFAVertex v : cand_succs) {
+        if (!contains(unhandled_succ_tops, v)) {
+            continue;
+        }
+        if (!has_intersection(unhandled_succ_tops.at(v), picked_tops)) {
+            continue; /* not adding any useful work that hasn't already been
+                       * done */
+        }
+        if (!is_subset_of(picked_tops, succ_tops.at(v))) {
+            continue; /* will not form a cbs */
+        }
+        picked_succs.insert(v);
+    }
+}
 
-        CharReach top_cr;
-        if (contains(top_reach, t)) {
-            top_cr = top_reach.at(t);
-        } else {
-            top_cr = CharReach::dot();
+/* See if we can expand the complete bipartite subgraph (cbs) specified by the
+ * picked tops/succs by adding more to either of the tops or succs.
+ */
+static
+void expandTopSuccCbs(const map<u32, flat_set<NFAVertex>> &top_succs,
+                      const map<NFAVertex, flat_set<u32>> &succ_tops,
+                      const map<u32, flat_set<NFAVertex>> &unhandled_top_succs,
+                      const map<NFAVertex, flat_set<u32>> &unhandled_succ_tops,
+                      flat_set<u32> &picked_tops,
+                      flat_set<NFAVertex> &picked_succs) {
+    /* Note: all picked (tops|succs) are equivalent */
+
+    /* Try to expand first (as we are more likely to succeed) on the side
+     * with fewest remaining things to be handled */
+
+    if (unhandled_top_succs.size() < unhandled_succ_tops.size()) {
+        expandCbsByTops(unhandled_top_succs, top_succs, succ_tops,
+                        picked_tops, picked_succs);
+        expandCbsBySuccs(unhandled_succ_tops, top_succs, succ_tops,
+                        picked_tops, picked_succs);
+    } else {
+        expandCbsBySuccs(unhandled_succ_tops, top_succs, succ_tops,
+                        picked_tops, picked_succs);
+        expandCbsByTops(unhandled_top_succs, top_succs, succ_tops,
+                        picked_tops, picked_succs);
+    }
+}
+
+static
+void markTopSuccAsHandled(NFAVertex start_v,
+                          const flat_set<u32> &handled_tops,
+                          const flat_set<NFAVertex> &handled_succs,
+                          map<u32, set<NFAVertex>> &tops_out,
+                          map<u32, flat_set<NFAVertex>> &unhandled_top_succs,
+                          map<NFAVertex, flat_set<u32>> &unhandled_succ_tops) {
+    for (u32 t : handled_tops) {
+        tops_out[t].insert(start_v);
+        assert(contains(unhandled_top_succs, t));
+        erase_all(&unhandled_top_succs[t], handled_succs);
+        if (unhandled_top_succs[t].empty()) {
+            unhandled_top_succs.erase(t);
         }
+    }
 
-        assert(!contains(tops, t));
+    for (NFAVertex v : handled_succs) {
+        assert(contains(unhandled_succ_tops, v));
+        erase_all(&unhandled_succ_tops[v], handled_tops);
+        if (unhandled_succ_tops[v].empty()) {
+            unhandled_succ_tops.erase(v);
+        }
+    }
+}
 
-        NFAVertex s = NGHolder::null_vertex();
-        flat_set<NFAVertex> succs;
-        insert(&succs, top.second);
+static
+void attemptToUseAsStart(const NGHolder &g,  NFAVertex u,
+                         const map<u32, CharReach> &top_reach,
+                         map<u32, flat_set<NFAVertex>> &unhandled_top_succs,
+                         map<NFAVertex, flat_set<u32>> &unhandled_succ_tops,
+                         map<u32, set<NFAVertex>> &tops_out) {
+    flat_set<u32> top_inter = unhandled_succ_tops.at(u);
+    flat_set<NFAVertex> succs;
+    for (NFAVertex v : adjacent_vertices_range(u, g)) {
+        if (!contains(unhandled_succ_tops, v)) {
+            return;
+        }
+        const flat_set<u32> &v_tops = unhandled_succ_tops.at(v);
+        flat_set<u32> new_inter;
+        auto ni_inserter = inserter(new_inter, new_inter.end());
+        set_intersection(top_inter.begin(), top_inter.end(),
+                         v_tops.begin(), v_tops.end(), ni_inserter);
+        top_inter = move(new_inter);
+        succs.insert(v);
+    }
 
-        for (auto v : top.second) {
-            if (!top_cr.isSubsetOf(g[v].char_reach)) {
-                continue;
-            }
+    if (top_inter.empty()) {
+        return;
+    }
 
-            flat_set<NFAVertex> vsuccs;
-            insert(&vsuccs, adjacent_vertices(v, g));
+    auto top_cr = calcTopVertexReach(top_inter, top_reach);
+    if (!top_cr.isSubsetOf(g[u].char_reach)) {
+        return;
+    }
 
-            if (succs != vsuccs) {
-                continue;
-            }
+    markTopSuccAsHandled(u, top_inter, succs, tops_out, unhandled_top_succs,
+                         unhandled_succ_tops);
+}
 
-            if (g[v].reports != g[g.start].reports) {
-                continue;
-            }
-            s = v;
-            break;
+/* We may have cases where a top triggers something that starts with a .* (or
+ * similar state). In these cases we can make use of that state as a start
+ * state.
+ */
+static
+void reusePredsAsStarts(const NGHolder &g, const map<u32, CharReach> &top_reach,
+                        map<u32, flat_set<NFAVertex>> &unhandled_top_succs,
+                        map<NFAVertex, flat_set<u32>> &unhandled_succ_tops,
+                        map<u32, set<NFAVertex>> &tops_out) {
+    /* create list of candidates first, to avoid issues of iter invalidation
+     * and determinism */
+    vector<NFAVertex> cand_starts;
+    for (NFAVertex u : unhandled_succ_tops | map_keys) {
+        if (hasSelfLoop(u, g)) {
+            cand_starts.push_back(u);
         }
+    }
+    sort(cand_starts.begin(), cand_starts.end(), make_index_ordering(g));
 
-        if (!s) {
-            s = add_vertex(g[g.start], g);
-            g[s].char_reach = top_cr;
-            for (auto v : top.second) {
-                add_edge(s, v, g);
-            }
+    for (NFAVertex u : cand_starts) {
+        if (!contains(unhandled_succ_tops, u)) {
+            continue;
         }
-        tops[t] = s;
+        attemptToUseAsStart(g, u, top_reach, unhandled_top_succs,
+                            unhandled_succ_tops, tops_out);
+     }
+}
+
+static
+void makeTopStates(NGHolder &g, map<u32, set<NFAVertex>> &tops_out,
+                   const map<u32, CharReach> &top_reach) {
+    /* Ideally, we want to add the smallest number of states to the graph for
+     * tops to turn on so that they can accurately trigger their successors.
+     *
+     * The relationships between tops and their successors forms a bipartite
+     * graph. Finding the optimal number of start states to add is equivalent to
+     * finding a minimal biclique coverings. Unfortunately, this is known to be
+     * NP-complete.
+     *
+     * Given this, we will just do something simple to avoid creating something
+     * truly wasteful:
+     * 1) Try to find any cyclic states which can act as their own start states
+     * 2) Pick a top or a succ to create a start state for and then try to find
+     *    the largest complete bipartite subgraph that it is part of.
+     */
+
+    map<u32, flat_set<NFAVertex>> top_succs;
+    map<NFAVertex, flat_set<u32>> succ_tops;
+    for (const auto &e : out_edges_range(g.start, g)) {
+        NFAVertex v = target(e, g);
+        for (u32 t : g[e].tops) {
+            top_succs[t].insert(v);
+            succ_tops[v].insert(t);
+        }
+    }
+
+    auto unhandled_top_succs = top_succs;
+    auto unhandled_succ_tops = succ_tops;
+
+    reusePredsAsStarts(g, top_reach, unhandled_top_succs, unhandled_succ_tops,
+                       tops_out);
+
+    /* Note: there may be successors which are equivalent (in terms of
+       top-triggering), it may be more efficient to discover this and treat them
+       as a unit. TODO */
+
+    while (!unhandled_succ_tops.empty()) {
+        assert(!unhandled_top_succs.empty());
+        flat_set<u32> u_tops;
+        flat_set<NFAVertex> u_succs;
+        pickNextTopStateToHandle(unhandled_top_succs, unhandled_succ_tops,
+                                 &u_tops, &u_succs);
+
+        expandTopSuccCbs(top_succs, succ_tops, unhandled_top_succs,
+                         unhandled_succ_tops, u_tops, u_succs);
+
+        /* create start vertex to handle this top/succ combination */
+        NFAVertex u = makeTopStartVertex(g, u_tops, u_succs, top_reach);
+
+        /* update maps */
+        markTopSuccAsHandled(u, u_tops, u_succs, tops_out, unhandled_top_succs,
+                             unhandled_succ_tops);
     }
+    assert(unhandled_top_succs.empty());
 
     // We are completely replacing the start vertex, so clear its reports.
     clear_out_edges(g.start, g);
     add_edge(g.start, g.startDs, g);
     g[g.start].reports.clear();
-
-    // Only retain reports (which we copied on add_vertex above) for new top
-    // vertices connected to accepts.
-    for (const auto &m : tops) {
-        NFAVertex v = m.second;
-        if (!edge(v, g.accept, g).second && !edge(v, g.acceptEod, g).second) {
-            g[v].reports.clear();
-        }
-    }
 }
 
 static
@@ -325,7 +562,8 @@ prepareGraph(const NGHolder &h_in, const ReportManager *rm,
              const map<u32, vector<vector<CharReach>>> &triggers,
              bool impl_test_only, const CompileContext &cc,
              ue2::unordered_map<NFAVertex, u32> &state_ids,
-             vector<BoundedRepeatData> &repeats, map<u32, NFAVertex> &tops) {
+             vector<BoundedRepeatData> &repeats,
+             map<u32, set<NFAVertex>> &tops) {
     assert(is_triggered(h_in) || fixed_depth_tops.empty());
 
     unique_ptr<NGHolder> h = cloneHolder(h_in);
@@ -335,15 +573,19 @@ prepareGraph(const NGHolder &h_in, const ReportManager *rm,
                    impl_test_only, cc.grey);
 
     // If we're building a rose/suffix, do the top dance.
+    flat_set<NFAVertex> topVerts;
     if (is_triggered(*h)) {
         makeTopStates(*h, tops, findTopReach(triggers));
+
+        for (const auto &vv : tops | map_values) {
+            insert(&topVerts, vv);
+        }
     }
 
     dropRedundantStartEdges(*h);
 
     // Do state numbering
-    state_ids = numberStates(*h, tops);
-    dropUnusedStarts(*h, state_ids);
+    state_ids = numberStates(*h, topVerts);
 
     // In debugging, we sometimes like to reverse the state numbering to stress
     // the NFA construction code.
@@ -389,14 +631,14 @@ constructNFA(const NGHolder &h_in, const ReportManager *rm,
 
     ue2::unordered_map<NFAVertex, u32> state_ids;
     vector<BoundedRepeatData> repeats;
-    map<u32, NFAVertex> tops;
+    map<u32, set<NFAVertex>> tops;
     unique_ptr<NGHolder> h
         = prepareGraph(h_in, rm, fixed_depth_tops, triggers, impl_test_only, cc,
                        state_ids, repeats, tops);
 
     // Quick exit: if we've got an embarrassment of riches, i.e. more states
     // than we can implement in our largest NFA model, bail here.
-    u32 numStates = countStates(*h, state_ids, false);
+    u32 numStates = countStates(state_ids);
     if (numStates > NFA_MAX_STATES) {
         DEBUG_PRINTF("Can't build an NFA with %u states\n", numStates);
         return nullptr;
@@ -469,13 +711,11 @@ aligned_unique_ptr<NFA> constructReversedNFA_i(const NGHolder &h_in, u32 hint,
     assert(h.kind == NFA_REV_PREFIX); /* triggered, raises internal callbacks */
 
     // Do state numbering.
-    auto state_ids = numberStates(h);
-
-    dropUnusedStarts(h, state_ids);
+    auto state_ids = numberStates(h, {});
 
     // Quick exit: if we've got an embarrassment of riches, i.e. more states
     // than we can implement in our largest NFA model, bail here.
-    u32 numStates = countStates(h, state_ids, false);
+    u32 numStates = countStates(state_ids);
     if (numStates > NFA_MAX_STATES) {
         DEBUG_PRINTF("Can't build an NFA with %u states\n", numStates);
         return nullptr;
@@ -483,7 +723,7 @@ aligned_unique_ptr<NFA> constructReversedNFA_i(const NGHolder &h_in, u32 hint,
 
     assert(sanityCheckGraph(h, state_ids));
 
-    map<u32, NFAVertex> tops; /* only the standards tops for nfas */
+    map<u32, set<NFAVertex>> tops; /* only the standards tops for nfas */
     set<NFAVertex> zombies;
     vector<BoundedRepeatData> repeats;
     map<NFAVertex, NFAStateSet> reportSquashMap;
@@ -518,7 +758,7 @@ u32 isImplementableNFA(const NGHolder &g, const ReportManager *rm,
     // Quick check: we can always implement an NFA with less than NFA_MAX_STATES
     // states. Note that top masks can generate extra states, so we account for
     // those here too.
-    if (num_vertices(g) + NFA_MAX_TOP_MASKS < NFA_MAX_STATES) {
+    if (num_vertices(g) + getTops(g).size() < NFA_MAX_STATES) {
         return true;
     }
 
@@ -539,12 +779,12 @@ u32 isImplementableNFA(const NGHolder &g, const ReportManager *rm,
 
     ue2::unordered_map<NFAVertex, u32> state_ids;
     vector<BoundedRepeatData> repeats;
-    map<u32, NFAVertex> tops;
+    map<u32, set<NFAVertex>> tops;
     unique_ptr<NGHolder> h
         = prepareGraph(g, rm, fixed_depth_tops, triggers, impl_test_only, cc,
                        state_ids, repeats, tops);
     assert(h);
-    u32 numStates = countStates(*h, state_ids, false);
+    u32 numStates = countStates(state_ids);
     if (numStates <= NFA_MAX_STATES) {
         return numStates;
     }
@@ -586,12 +826,12 @@ u32 countAccelStates(const NGHolder &g, const ReportManager *rm,
 
     ue2::unordered_map<NFAVertex, u32> state_ids;
     vector<BoundedRepeatData> repeats;
-    map<u32, NFAVertex> tops;
+    map<u32, set<NFAVertex>> tops;
     unique_ptr<NGHolder> h
         = prepareGraph(g, rm, fixed_depth_tops, triggers, impl_test_only, cc,
                        state_ids, repeats, tops);
 
-    if (!h || countStates(*h, state_ids, false) > NFA_MAX_STATES) {
+    if (!h || countStates(state_ids) > NFA_MAX_STATES) {
         DEBUG_PRINTF("not constructible\n");
         return NFA_MAX_ACCEL_STATES + 1;
     }
index 39788570d39ac0385b6011ae460b28d11a74495d..71c9a05e329dd316ea0b2aec266d2844913aa830 100644 (file)
@@ -36,7 +36,6 @@
 #include "nfa/rdfa.h"
 #include "ng_holder.h"
 #include "ng_mcclellan_internal.h"
-#include "ng_restructuring.h"
 #include "ng_squash.h"
 #include "ng_util.h"
 #include "ue2common.h"
@@ -348,10 +347,11 @@ public:
     using StateMap = typename Automaton_Traits::StateMap;
 
     Automaton_Base(const ReportManager *rm_in, const NGHolder &graph_in,
-                   const flat_set<NFAVertex> &unused_in, bool single_trigger,
+                   bool single_trigger,
                    const vector<vector<CharReach>> &triggers, bool prunable_in)
         : rm(rm_in), graph(graph_in), numStates(num_vertices(graph)),
-          unused(unused_in), init(Automaton_Traits::init_states(numStates)),
+          unused(getRedundantStarts(graph_in)),
+          init(Automaton_Traits::init_states(numStates)),
           initDS(Automaton_Traits::init_states(numStates)),
           squash(Automaton_Traits::init_states(numStates)),
           accept(Automaton_Traits::init_states(numStates)),
@@ -444,7 +444,7 @@ private:
 public:
     const NGHolder &graph;
     u32 numStates;
-    const flat_set<NFAVertex> &unused;
+    const flat_set<NFAVertex> unused;
     vector<NFAVertex> v_by_index;
     vector<CharReach> cr_by_index; /* pre alpha'ed */
     StateSet init;
@@ -482,9 +482,9 @@ struct Big_Traits {
 class Automaton_Big : public Automaton_Base<Big_Traits> {
 public:
     Automaton_Big(const ReportManager *rm_in, const NGHolder &graph_in,
-                  const flat_set<NFAVertex> &unused_in, bool single_trigger,
+                  bool single_trigger,
                   const vector<vector<CharReach>> &triggers, bool prunable_in)
-        : Automaton_Base(rm_in, graph_in, unused_in, single_trigger, triggers,
+        : Automaton_Base(rm_in, graph_in, single_trigger, triggers,
                          prunable_in) {}
 };
 
@@ -510,14 +510,36 @@ struct Graph_Traits {
 class Automaton_Graph : public Automaton_Base<Graph_Traits> {
 public:
     Automaton_Graph(const ReportManager *rm_in, const NGHolder &graph_in,
-                  const flat_set<NFAVertex> &unused_in, bool single_trigger,
-                  const vector<vector<CharReach>> &triggers, bool prunable_in)
-        : Automaton_Base(rm_in, graph_in, unused_in, single_trigger, triggers,
+                    bool single_trigger,
+                    const vector<vector<CharReach>> &triggers, bool prunable_in)
+        : Automaton_Base(rm_in, graph_in, single_trigger, triggers,
                          prunable_in) {}
 };
 
 } // namespace
 
+static
+bool startIsRedundant(const NGHolder &g) {
+    set<NFAVertex> start;
+    set<NFAVertex> startDs;
+
+    insert(&start, adjacent_vertices(g.start, g));
+    insert(&startDs, adjacent_vertices(g.startDs, g));
+
+    return start == startDs;
+}
+
+flat_set<NFAVertex> getRedundantStarts(const NGHolder &g) {
+    flat_set<NFAVertex> dead;
+    if (startIsRedundant(g)) {
+        dead.insert(g.start);
+    }
+    if (proper_out_degree(g.startDs, g) == 0) {
+        dead.insert(g.startDs);
+    }
+    return dead;
+}
+
 unique_ptr<raw_dfa> buildMcClellan(const NGHolder &graph,
                                    const ReportManager *rm, bool single_trigger,
                                    const vector<vector<CharReach>> &triggers,
@@ -526,8 +548,6 @@ unique_ptr<raw_dfa> buildMcClellan(const NGHolder &graph,
         return nullptr;
     }
 
-    auto unused = findUnusedStates(graph);
-
     DEBUG_PRINTF("attempting to build ?%d? mcclellan\n", (int)graph.kind);
     assert(allMatchStatesHaveReports(graph));
 
@@ -553,8 +573,7 @@ unique_ptr<raw_dfa> buildMcClellan(const NGHolder &graph,
     if (numStates <= NFA_STATE_LIMIT) {
         /* Fast path. Automaton_Graph uses a bitfield internally to represent
          * states and is quicker than Automaton_Big. */
-        Automaton_Graph n(rm, graph, unused, single_trigger, triggers,
-                          prunable);
+        Automaton_Graph n(rm, graph, single_trigger, triggers, prunable);
         if (determinise(n, rdfa->states, state_limit)) {
             DEBUG_PRINTF("state limit exceeded\n");
             return nullptr; /* over state limit */
@@ -566,7 +585,7 @@ unique_ptr<raw_dfa> buildMcClellan(const NGHolder &graph,
         rdfa->alpha_remap = n.alpha;
     } else {
         /* Slow path. Too many states to use Automaton_Graph. */
-        Automaton_Big n(rm, graph, unused, single_trigger, triggers, prunable);
+        Automaton_Big n(rm, graph, single_trigger, triggers, prunable);
         if (determinise(n, rdfa->states, state_limit)) {
             DEBUG_PRINTF("state limit exceeded\n");
             return nullptr; /* over state limit */
index 22fcf01e677aeea6d9f167b5cec8c57bc0310688..b78dac3b1266deba2d0253648b8ed7f24c89e7dc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Intel Corporation
+ * Copyright (c) 2015-2016, Intel Corporation
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -36,7 +36,6 @@
 #include "ue2common.h"
 #include "nfa/mcclellancompile.h"
 #include "nfagraph/ng_holder.h"
-#include "nfagraph/ng_restructuring.h" // for NO_STATE
 #include "util/charreach.h"
 #include "util/graph_range.h"
 #include "util/ue2_containers.h"
@@ -69,6 +68,13 @@ void markToppableStarts(const NGHolder &g, const flat_set<NFAVertex> &unused,
                         const std::vector<std::vector<CharReach>> &triggers,
                         boost::dynamic_bitset<> *out);
 
+/**
+ * \brief Returns a set of start vertices that will not participate in an
+ * implementation of this graph. These are either starts with no successors or
+ * starts which are redundant with startDs.
+ */
+flat_set<NFAVertex> getRedundantStarts(const NGHolder &g);
+
 template<typename autom>
 void transition_graph(autom &nfa, const std::vector<NFAVertex> &vByStateId,
                       const typename autom::StateSet &in,
index c85860c7b9b8143806a5c2f317a291d7dfa08875..4699033063873260a0fc23d13f03a6e02e698ff9 100644 (file)
@@ -49,37 +49,71 @@ namespace ue2 {
 /** Connect the start vertex to each of the vertices in \p tops. This is useful
  * temporarily for when we need to run a graph algorithm that expects a single
  * source vertex. */
-void wireStartToTops(NGHolder &g, const map<u32, NFAVertex> &tops,
-                     vector<NFAEdge> &topEdges) {
-    for (const auto &top : tops) {
-        NFAVertex v = top.second;
+static
+void wireStartToTops(NGHolder &g, const flat_set<NFAVertex> &tops,
+                     vector<NFAEdge> &tempEdges) {
+    for (NFAVertex v : tops) {
         assert(!isLeafNode(v, g));
 
         const NFAEdge &e = add_edge(g.start, v, g).first;
-        topEdges.push_back(e);
+        tempEdges.push_back(e);
+    }
+}
+
+/**
+ * Returns true if start's successors (aside from startDs) are subset of
+ * startDs's proper successors or if start has no successors other than startDs.
+ */
+static
+bool startIsRedundant(const NGHolder &g) {
+    /* We ignore startDs as the self-loop may have been stripped as an
+     * optimisation for repeats (improveLeadingRepeats()). */
+    set<NFAVertex> start;
+    insert(&start,  adjacent_vertices_range(g.start, g));
+    start.erase(g.startDs);
+
+    // Trivial case: start has no successors other than startDs.
+    if (start.empty()) {
+        DEBUG_PRINTF("start has no out-edges other than to startDs\n");
+        return true;
+    }
+
+    set<NFAVertex> startDs;
+    insert(&startDs,  adjacent_vertices_range(g.startDs, g));
+    startDs.erase(g.startDs);
+
+    if (!is_subset_of(start, startDs)) {
+        DEBUG_PRINTF("out-edges of start and startDs aren't equivalent\n");
+        return false;
     }
+
+    return true;
 }
 
 static
-void getStateOrdering(NGHolder &g, const map<u32, NFAVertex> &tops,
+void getStateOrdering(NGHolder &g, const flat_set<NFAVertex> &tops,
                       vector<NFAVertex> &ordering) {
     // First, wire up our "tops" to start so that we have a single source,
     // which will give a nicer topo order.
-    vector<NFAEdge> topEdges;
-    wireStartToTops(g, tops, topEdges);
+    vector<NFAEdge> tempEdges;
+    wireStartToTops(g, tops, tempEdges);
 
     renumberGraphVertices(g);
 
     vector<NFAVertex> temp = getTopoOrdering(g);
 
-    remove_edges(topEdges, g);
+    remove_edges(tempEdges, g);
 
     // Move {start, startDs} to the end, so they'll be first when we reverse
-    // the ordering.
+    // the ordering (if they are required).
     temp.erase(remove(temp.begin(), temp.end(), g.startDs));
     temp.erase(remove(temp.begin(), temp.end(), g.start));
-    temp.push_back(g.startDs);
-    temp.push_back(g.start);
+    if (proper_out_degree(g.startDs, g)) {
+        temp.push_back(g.startDs);
+    }
+    if (!startIsRedundant(g)) {
+        temp.push_back(g.start);
+    }
 
     // Walk ordering, remove vertices that shouldn't be participating in state
     // numbering, such as accepts.
@@ -149,16 +183,15 @@ void optimiseTightLoops(const NGHolder &g, vector<NFAVertex> &ordering) {
             continue;
         }
 
-        DEBUG_PRINTF("moving vertex %u next to %u\n",
-                     g[v].index, g[u].index);
+        DEBUG_PRINTF("moving vertex %u next to %u\n", g[v].index, g[u].index);
 
         ordering.erase(v_it);
         ordering.insert(++u_it, v);
     }
 }
 
-ue2::unordered_map<NFAVertex, u32>
-numberStates(NGHolder &h, const map<u32, NFAVertex> &tops) {
+unordered_map<NFAVertex, u32>
+numberStates(NGHolder &h, const flat_set<NFAVertex> &tops) {
     DEBUG_PRINTF("numbering states for holder %p\n", &h);
 
     vector<NFAVertex> ordering;
@@ -166,15 +199,10 @@ numberStates(NGHolder &h, const map<u32, NFAVertex> &tops) {
 
     optimiseTightLoops(h, ordering);
 
-    ue2::unordered_map<NFAVertex, u32> states = getStateIndices(h, ordering);
-
-    return states;
+    return getStateIndices(h, ordering);
 }
 
-u32 countStates(const NGHolder &g,
-                const ue2::unordered_map<NFAVertex, u32> &state_ids,
-                bool addTops) {
-    /* TODO: smarter top state allocation, move to limex? */
+u32 countStates(const unordered_map<NFAVertex, u32> &state_ids) {
     if (state_ids.empty()) {
         return 0;
     }
@@ -185,168 +213,9 @@ u32 countStates(const NGHolder &g,
             max_state = max(m.second, max_state);
         }
     }
-
     u32 num_states = max_state + 1;
 
-    assert(contains(state_ids, g.start));
-    if (addTops && is_triggered(g) && state_ids.at(g.start) != NO_STATE) {
-        num_states--;
-        set<u32> tops;
-        for (auto e : out_edges_range(g.start, g)) {
-            insert(&tops, g[e].tops);
-        }
-        num_states += tops.size();
-    }
-
     return num_states;
 }
 
-/**
- * Returns true if start leads to all of startDs's proper successors or if
- * start has no successors other than startDs.
- */
-static
-bool startIsRedundant(const NGHolder &g) {
-    set<NFAVertex> start, startDs;
-
-    for (const auto &e : out_edges_range(g.start, g)) {
-        NFAVertex v = target(e, g);
-        if (v == g.startDs) {
-            continue;
-        }
-        start.insert(v);
-    }
-
-    for (const auto &e : out_edges_range(g.startDs, g)) {
-        NFAVertex v = target(e, g);
-        if (v == g.startDs) {
-            continue;
-        }
-        startDs.insert(v);
-    }
-
-    // Trivial case: start has no successors other than startDs.
-    if (start.empty()) {
-        DEBUG_PRINTF("start has no out-edges other than to startDs\n");
-        return true;
-    }
-
-    if (start != startDs) {
-        DEBUG_PRINTF("out-edges of start and startDs aren't equivalent\n");
-        return false;
-    }
-
-    return true;
-}
-
-/** One final, FINAL optimisation. Drop either start or startDs if it's unused
- * in this graph. We leave this until this late because having both vertices in
- * the graph, with fixed state indices, is useful for merging and other
- * analyses. */
-void dropUnusedStarts(NGHolder &g, ue2::unordered_map<NFAVertex, u32> &states) {
-    u32 adj = 0;
-
-    if (startIsRedundant(g)) {
-        DEBUG_PRINTF("dropping unused start\n");
-        states[g.start] = NO_STATE;
-        adj++;
-    }
-
-    if (proper_out_degree(g.startDs, g) == 0) {
-        DEBUG_PRINTF("dropping unused startDs\n");
-        states[g.startDs] = NO_STATE;
-        adj++;
-    }
-
-    if (!adj) {
-        DEBUG_PRINTF("both start and startDs must remain\n");
-        return;
-    }
-
-    // We have removed one or both of the starts. Walk the non-special vertices
-    // in the graph with state indices assigned to them and subtract
-    // adj from all of them.
-    for (auto v : vertices_range(g)) {
-        u32 &state = states[v]; // note ref
-        if (state == NO_STATE) {
-            continue;
-        }
-        if (is_any_start(v, g)) {
-            assert(state <= 1);
-            state = 0; // one start remains
-        } else {
-            assert(!is_special(v, g));
-            assert(state >= adj);
-            state -= adj;
-        }
-    }
-}
-
-flat_set<NFAVertex> findUnusedStates(const NGHolder &g) {
-    flat_set<NFAVertex> dead;
-    if (startIsRedundant(g)) {
-        dead.insert(g.start);
-    }
-    if (proper_out_degree(g.startDs, g) == 0) {
-        dead.insert(g.startDs);
-    }
-    return dead;
-}
-
-/** Construct a reversed copy of an arbitrary NGHolder, mapping starts to
- * accepts. */
-void reverseHolder(const NGHolder &g_in, NGHolder &g) {
-    // Make the BGL do the grunt work.
-    ue2::unordered_map<NFAVertex, NFAVertex> vertexMap;
-    boost::transpose_graph(g_in.g, g.g,
-                orig_to_copy(boost::make_assoc_property_map(vertexMap)).
-                vertex_index_map(get(&NFAGraphVertexProps::index, g_in.g)));
-
-    // The transpose_graph operation will have created extra copies of our
-    // specials. We have to rewire their neighbours to the 'real' specials and
-    // delete them.
-    NFAVertex start = vertexMap[g_in.acceptEod];
-    NFAVertex startDs = vertexMap[g_in.accept];
-    NFAVertex accept = vertexMap[g_in.startDs];
-    NFAVertex acceptEod = vertexMap[g_in.start];
-
-    // Successors of starts.
-    for (const auto &e : out_edges_range(start, g)) {
-        NFAVertex v = target(e, g);
-        add_edge(g.start, v, g[e], g);
-    }
-    for (const auto &e : out_edges_range(startDs, g)) {
-        NFAVertex v = target(e, g);
-        add_edge(g.startDs, v, g[e], g);
-    }
-
-    // Predecessors of accepts.
-    for (const auto &e : in_edges_range(accept, g)) {
-        NFAVertex u = source(e, g);
-        add_edge(u, g.accept, g[e], g);
-    }
-    for (const auto &e : in_edges_range(acceptEod, g)) {
-        NFAVertex u = source(e, g);
-        add_edge(u, g.acceptEod, g[e], g);
-    }
-
-    // Remove our impostors.
-    clear_vertex(start, g);
-    remove_vertex(start, g);
-    clear_vertex(startDs, g);
-    remove_vertex(startDs, g);
-    clear_vertex(accept, g);
-    remove_vertex(accept, g);
-    clear_vertex(acceptEod, g);
-    remove_vertex(acceptEod, g);
-
-    // Renumber so that g's properties (number of vertices, edges) are
-    // accurate.
-    g.renumberVertices();
-    g.renumberEdges();
-
-    assert(num_vertices(g) == num_vertices(g_in));
-    assert(num_edges(g) == num_edges(g_in));
-}
-
 } // namespace ue2
index 5e244bf67b1fa8faac61a1c2dc579bcba5396386..bbd478d53768dd931e66d18fe47d7b984fe3ddae 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Intel Corporation
+ * Copyright (c) 2015-2016, Intel Corporation
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
 #include "ue2common.h"
 #include "util/ue2_containers.h"
 
-#include <map>
-#include <vector>
-
 namespace ue2 {
 
-class NGHolder;
-
-/** Construct a reversed copy of an arbitrary NGHolder, mapping starts to
- * accepts. */
-void reverseHolder(const NGHolder &g, NGHolder &out);
-
-/** Connect the start vertex to each of the vertices in \p tops. This is useful
- * temporarily for when we need to run a graph algorithm that expects a single
- * source vertex. */
-void wireStartToTops(NGHolder &g, const std::map<u32, NFAVertex> &tops,
-                     std::vector<NFAEdge> &topEdges);
-
 /**
  * \brief Special state index value meaning that the vertex will not
  * participate in an (NFA/DFA/etc) implementation.
@@ -63,30 +48,14 @@ static constexpr u32 NO_STATE = ~0;
 /**
  * \brief Gives each participating vertex in the graph a unique state index.
  */
-ue2::unordered_map<NFAVertex, u32>
-numberStates(NGHolder &h,
-             const std::map<u32, NFAVertex> &tops = std::map<u32, NFAVertex>{});
+unordered_map<NFAVertex, u32>
+numberStates(NGHolder &h, const flat_set<NFAVertex> &tops);
 
 /**
  * \brief Counts the number of states (vertices with state indices) in the
  * graph.
- *
- * If addTops is true, also accounts for states that will be constructed for
- * each unique top.
- */
-u32 countStates(const NGHolder &g,
-                const ue2::unordered_map<NFAVertex, u32> &state_ids,
-                bool addTops = true);
-
-/** Optimisation: drop unnecessary start states. */
-void dropUnusedStarts(NGHolder &g, ue2::unordered_map<NFAVertex, u32> &states);
-
-/**
- * \brief Returns a set of vertices that will not participate in an
- * implementation (NFA, DFA etc) of this graph. For example, starts with no
- * successors.
  */
-flat_set<NFAVertex> findUnusedStates(const NGHolder &g);
+u32 countStates(const unordered_map<NFAVertex, u32> &state_ids);
 
 } // namespace ue2
 
index fd6dfc3e8a809fe43afe7d9ea8e88f7e7c17ade7..3326d6f4f37dee6591ddd90f719fb8b08d8bba75 100644 (file)
@@ -39,7 +39,6 @@
 #include "ng_limex.h"
 #include "ng_redundancy.h"
 #include "ng_region.h"
-#include "ng_restructuring.h"
 #include "ng_uncalc_components.h"
 #include "ng_util.h"
 #include "ue2common.h"
 #include <set>
 #include <vector>
 
+#include <boost/range/adaptor/map.hpp>
+
 using namespace std;
+using boost::adaptors::map_values;
 
 namespace ue2 {
 
 static const u32 FAST_STATE_LIMIT = 256; /**< largest possible desirable NFA */
 
 /** Sentinel value meaning no component has yet been selected. */
-static const u32 NO_COMPONENT = 0xffffffffu;
+static const u32 NO_COMPONENT = ~0U;
 
-static
-vector<NFAVertex> getSortedVA(const NGHolder &g,
-            const ue2::unordered_map<NFAVertex, u32> &state_ids) {
-    vector<NFAVertex> out;
-    out.reserve(num_vertices(g));
-
-    for (auto v : vertices_range(g)) {
-        assert(contains(state_ids, v));
-        if (state_ids.at(v) == NO_STATE) {
-            continue;
+static const u32 UNUSED_STATE = ~0U;
+
+namespace {
+struct ranking_info {
+    explicit ranking_info(const NGHolder &h) : to_vertex(getTopoOrdering(h)) {
+        u32 rank = 0;
+
+        reverse(to_vertex.begin(), to_vertex.end());
+
+        for (NFAVertex v : to_vertex) {
+            to_rank[v] = rank++;
         }
-        out.push_back(v);
-    }
 
-    // Order vertices by their state indices.
-    sort(begin(out), end(out), [&state_ids](NFAVertex a, NFAVertex b) {
-        return state_ids.at(a) < state_ids.at(b);
-    });
+        for (NFAVertex v : vertices_range(h)) {
+            if (!contains(to_rank, v)) {
+                to_rank[v] = UNUSED_STATE;
+            }
+        }
+    }
 
-#ifndef NDEBUG
-    // State indices should match vector indices.
-    for (u32 i = 0; i < out.size(); i++) {
-        assert(state_ids.at(out.at(i)) == i);
+    NFAVertex at(u32 ranking) const { return to_vertex.at(ranking); }
+    u32 get(NFAVertex v) const { return to_rank.at(v); }
+    u32 size() const { return (u32)to_vertex.size(); }
+    u32 add_to_tail(NFAVertex v) {
+        u32 rank = size();
+        to_rank[v] = rank;
+        to_vertex.push_back(v);
+        return rank;
     }
-#endif
 
-    return out;
+private:
+    vector<NFAVertex> to_vertex;
+    unordered_map<NFAVertex, u32> to_rank;
+};
 }
 
 static never_inline
@@ -122,9 +131,9 @@ bool cplVerticesMatch(const NGHolder &ga, NFAVertex va,
 }
 
 static never_inline
-u32 cplCommonReachAndSimple(const NGHolder &ga, const vector<NFAVertex> &a,
-                            const NGHolder &gb, const vector<NFAVertex> &b) {
-    u32 ml = min(a.size(), b.size());
+u32 cplCommonReachAndSimple(const NGHolder &ga, const ranking_info &a_ranking,
+                            const NGHolder &gb, const ranking_info &b_ranking) {
+    u32 ml = min(a_ranking.size(), b_ranking.size());
     if (ml > 65535) {
         ml = 65535;
     }
@@ -133,7 +142,7 @@ u32 cplCommonReachAndSimple(const NGHolder &ga, const vector<NFAVertex> &a,
     // "startedness" properties.
     u32 max = 0;
     for (; max < ml; max++) {
-        if (!cplVerticesMatch(ga, a[max], gb, b[max])) {
+        if (!cplVerticesMatch(ga, a_ranking.at(max), gb, b_ranking.at(max))) {
             break;
         }
     }
@@ -141,34 +150,30 @@ u32 cplCommonReachAndSimple(const NGHolder &ga, const vector<NFAVertex> &a,
     return max;
 }
 
-u32 commonPrefixLength(const NGHolder &ga,
-                       const ue2::unordered_map<NFAVertex, u32> &a_state_ids,
-                       const NGHolder &gb,
-                       const ue2::unordered_map<NFAVertex, u32> &b_state_ids) {
-    vector<NFAVertex> a = getSortedVA(ga, a_state_ids);
-    vector<NFAVertex> b = getSortedVA(gb, b_state_ids);
-
+static
+u32 commonPrefixLength(const NGHolder &ga, const ranking_info &a_ranking,
+                       const NGHolder &gb, const ranking_info &b_ranking) {
     /* upper bound on the common region based on local properties */
-    u32 max = cplCommonReachAndSimple(ga, a, gb, b);
+    u32 max = cplCommonReachAndSimple(ga, a_ranking, gb, b_ranking);
     DEBUG_PRINTF("cpl upper bound %u\n", max);
 
     while (max > 0) {
-        bool ok = true;
-
         /* shrink max region based on in-edges from outside the region */
         for (size_t j = max; j > 0; j--) {
-            for (auto u : inv_adjacent_vertices_range(a[j - 1], ga)) {
-                u32 state_id = a_state_ids.at(u);
-                if (state_id != NO_STATE && state_id >= max) {
+            NFAVertex a_v = a_ranking.at(j - 1);
+            NFAVertex b_v = b_ranking.at(j - 1);
+            for (auto u : inv_adjacent_vertices_range(a_v, ga)) {
+                u32 state_id = a_ranking.get(u);
+                if (state_id != UNUSED_STATE && state_id >= max) {
                     max = j - 1;
                     DEBUG_PRINTF("lowering max to %u\n", max);
                     goto next_vertex;
                 }
             }
 
-            for (auto u : inv_adjacent_vertices_range(b[j - 1], gb)) {
-                u32 state_id = b_state_ids.at(u);
-                if (state_id != NO_STATE && state_id >= max) {
+            for (auto u : inv_adjacent_vertices_range(b_v, gb)) {
+                u32 state_id = b_ranking.get(u);
+                if (state_id != UNUSED_STATE && state_id >= max) {
                     max = j - 1;
                     DEBUG_PRINTF("lowering max to %u\n", max);
                     goto next_vertex;
@@ -180,14 +185,13 @@ u32 commonPrefixLength(const NGHolder &ga,
 
         /* Ensure that every pair of vertices has same out-edges to vertices in
            the region. */
-        for (size_t i = 0; ok && i < max; i++) {
+        for (size_t i = 0; i < max; i++) {
             size_t a_count = 0;
             size_t b_count = 0;
 
-            NGHolder::out_edge_iterator ei, ee;
-            for (tie(ei, ee) = out_edges(a[i], ga); ok && ei != ee; ++ei) {
-                u32 sid = a_state_ids.at(target(*ei, ga));
-                if (sid == NO_STATE || sid >= max) {
+            for (NFAEdge a_edge : out_edges_range(a_ranking.at(i), ga)) {
+                u32 sid = a_ranking.get(target(a_edge, ga));
+                if (sid == UNUSED_STATE || sid >= max) {
                     continue;
                 }
 
@@ -195,28 +199,26 @@ u32 commonPrefixLength(const NGHolder &ga,
 
                 NFAEdge b_edge;
                 bool has_b_edge;
-                tie(b_edge, has_b_edge) = edge(b[i], b[sid], gb);
+                tie(b_edge, has_b_edge) = edge(b_ranking.at(i),
+                                               b_ranking.at(sid), gb);
 
                 if (!has_b_edge) {
                     max = i;
-                    ok = false;
                     DEBUG_PRINTF("lowering max to %u due to edge %zu->%u\n",
                                  max, i, sid);
-                    break;
+                    goto try_smaller;
                 }
 
-                if (ga[*ei].tops != gb[b_edge].tops) {
+                if (ga[a_edge].tops != gb[b_edge].tops) {
                     max = i;
-                    ok = false;
                     DEBUG_PRINTF("tops don't match on edge %zu->%u\n", i, sid);
+                    goto try_smaller;
                 }
             }
 
-            NGHolder::adjacency_iterator ai, ae;
-            for (tie(ai, ae) = adjacent_vertices(b[i], gb); ok && ai != ae;
-                 ++ai) {
-                u32 sid = b_state_ids.at(*ai);
-                if (sid == NO_STATE || sid >= max) {
+            for (NFAVertex b_v : adjacent_vertices_range(b_ranking.at(i), gb)) {
+                u32 sid = b_ranking.get(b_v);
+                if (sid == UNUSED_STATE || sid >= max) {
                     continue;
                 }
 
@@ -225,28 +227,32 @@ u32 commonPrefixLength(const NGHolder &ga,
 
             if (a_count != b_count) {
                 max = i;
-                DEBUG_PRINTF("lowering max to %u due to a,b count "
-                             "(a_count=%zu, b_count=%zu)\n", max, a_count,
-                             b_count);
-                ok = false;
+                DEBUG_PRINTF("lowering max to %u due to a,b count (a_count=%zu,"
+                             " b_count=%zu)\n", max, a_count, b_count);
+                goto try_smaller;
             }
         }
 
-        if (ok) {
-            DEBUG_PRINTF("survived checks, returning cpl %u\n", max);
-            return max;
-        }
+        DEBUG_PRINTF("survived checks, returning cpl %u\n", max);
+        return max;
+    try_smaller:;
     }
 
     DEBUG_PRINTF("failed to find any common region\n");
     return 0;
 }
 
+u32 commonPrefixLength(const NGHolder &ga, const NGHolder &gb) {
+    return commonPrefixLength(ga, ranking_info(ga), gb, ranking_info(gb));
+}
+
 static never_inline
-void mergeNfa(NGHolder &dest, vector<NFAVertex> &destStateMap,
-              ue2::unordered_map<NFAVertex, u32> &dest_state_ids,
-              NGHolder &vic, vector<NFAVertex> &vicStateMap,
-              size_t common_len) {
+void mergeNfaComponent(NGHolder &dest, const NGHolder &vic, size_t common_len) {
+    assert(&dest != &vic);
+
+    auto dest_info = ranking_info(dest);
+    auto vic_info = ranking_info(vic);
+
     map<NFAVertex, NFAVertex> vmap; // vic -> dest
 
     vmap[vic.start]     = dest.start;
@@ -255,22 +261,20 @@ void mergeNfa(NGHolder &dest, vector<NFAVertex> &destStateMap,
     vmap[vic.acceptEod] = dest.acceptEod;
     vmap[nullptr] = nullptr;
 
-    u32 stateNum = countStates(dest, dest_state_ids);
-
     // For vertices in the common len, add to vmap and merge in the reports, if
     // any.
     for (u32 i = 0; i < common_len; i++) {
-        NFAVertex v_old = vicStateMap[i], v = destStateMap[i];
+        NFAVertex v_old = vic_info.at(i);
+        NFAVertex v = dest_info.at(i);
         vmap[v_old] = v;
 
         const auto &reports = vic[v_old].reports;
         dest[v].reports.insert(reports.begin(), reports.end());
     }
 
-    // Add in vertices beyond the common len, giving them state numbers
-    // starting at stateNum.
-    for (u32 i = common_len; i < vicStateMap.size(); i++) {
-        NFAVertex v_old = vicStateMap[i];
+    // Add in vertices beyond the common len
+    for (u32 i = common_len; i < vic_info.size(); i++) {
+        NFAVertex v_old = vic_info.at(i);
 
         if (is_special(v_old, vic)) {
             // Dest already has start vertices, just merge the reports.
@@ -282,15 +286,17 @@ void mergeNfa(NGHolder &dest, vector<NFAVertex> &destStateMap,
         }
 
         NFAVertex v = add_vertex(vic[v_old], dest);
-        dest_state_ids[v] = stateNum++;
+        dest_info.add_to_tail(v);
         vmap[v_old] = v;
     }
 
     /* add edges */
     DEBUG_PRINTF("common_len=%zu\n", common_len);
     for (const auto &e : edges_range(vic)) {
-        NFAVertex u_old = source(e, vic), v_old = target(e, vic);
-        NFAVertex u = vmap[u_old], v = vmap[v_old];
+        NFAVertex u_old = source(e, vic);
+        NFAVertex v_old = target(e, vic);
+        NFAVertex u = vmap[u_old];
+        NFAVertex v = vmap[v_old];
         bool uspecial = is_special(u, dest);
         bool vspecial = is_special(v, dest);
 
@@ -301,15 +307,14 @@ void mergeNfa(NGHolder &dest, vector<NFAVertex> &destStateMap,
 
         // We're in the common region if v's state ID is low enough, unless v
         // is a special (an accept), in which case we use u's state ID.
-        assert(contains(dest_state_ids, v));
-        bool in_common_region = dest_state_ids.at(v) < common_len;
-        if (vspecial && dest_state_ids.at(u) < common_len) {
+        bool in_common_region = dest_info.get(v) < common_len;
+        if (vspecial && dest_info.get(u) < common_len) {
             in_common_region = true;
         }
 
         DEBUG_PRINTF("adding idx=%u (state %u) -> idx=%u (state %u)%s\n",
-                     dest[u].index, dest_state_ids.at(u),
-                     dest[v].index, dest_state_ids.at(v),
+                     dest[u].index, dest_info.get(u),
+                     dest[v].index, dest_info.get(v),
                      in_common_region ? " [common]" : "");
 
         if (in_common_region) {
@@ -337,18 +342,6 @@ void mergeNfa(NGHolder &dest, vector<NFAVertex> &destStateMap,
     dest.renumberVertices();
 }
 
-static never_inline
-void mergeNfaComponent(NGHolder &pholder, NGHolder &vholder, size_t cpl) {
-    assert(&pholder != &vholder);
-
-    auto v_state_ids = numberStates(vholder);
-    auto p_state_ids = numberStates(pholder);
-    auto vhvmap = getSortedVA(vholder, v_state_ids);
-    auto phvmap = getSortedVA(pholder, p_state_ids);
-
-    mergeNfa(pholder, phvmap, p_state_ids, vholder, vhvmap, cpl);
-}
-
 namespace {
 struct NfaMergeCandidateH {
     NfaMergeCandidateH(size_t cpl_in, NGHolder *first_in, NGHolder *second_in,
@@ -373,14 +366,19 @@ struct NfaMergeCandidateH {
 
 /** Returns true if graphs \p h1 and \p h2 can (and should) be merged. */
 static
-bool shouldMerge(NGHolder &ha,
-                 const ue2::unordered_map<NFAVertex, u32> &a_state_ids,
-                 NGHolder &hb,
-                 const ue2::unordered_map<NFAVertex, u32> &b_state_ids,
-                 size_t cpl, const ReportManager *rm,
-                 const CompileContext &cc) {
-    size_t combinedStateCount =
-        countStates(ha, a_state_ids) + countStates(hb, b_state_ids) - cpl;
+bool shouldMerge(const NGHolder &ha, const NGHolder &hb, size_t cpl,
+                 const ReportManager *rm, const CompileContext &cc) {
+    size_t combinedStateCount = num_vertices(ha) + num_vertices(hb) - cpl;
+
+    combinedStateCount -= 2 * 2; /* discount accepts from both */
+
+    if (is_triggered(ha)) {
+        /* allow for a state for each top, ignore existing starts */
+        combinedStateCount -= 2; /* for start, startDs */
+        auto tops = getTops(ha);
+        insert(&tops, getTops(hb));
+        combinedStateCount += tops.size();
+    }
 
     if (combinedStateCount > FAST_STATE_LIMIT) {
         // More complex implementability check.
@@ -423,11 +421,13 @@ void buildNfaMergeQueue(const vector<NGHolder *> &cluster,
 
     // First, make sure all holders have numbered states and collect their
     // counts.
-    vector<ue2::unordered_map<NFAVertex, u32>> states_map(cs);
+    vector<ranking_info> states_map;
+    states_map.reserve(cs);
     for (size_t i = 0; i < cs; i++) {
         assert(cluster[i]);
-        NGHolder &g = *(cluster[i]);
-        states_map[i] = numberStates(g);
+        assert(states_map.size() == i);
+        const NGHolder &g = *(cluster[i]);
+        states_map.emplace_back(g);
     }
 
     vector<u16> seen_cpl(cs * cs, 0);
@@ -536,11 +536,9 @@ bool mergeableStarts(const NGHolder &h1, const NGHolder &h2) {
 }
 
 /** Merge graph \p ga into graph \p gb. Returns false on failure. */
-bool mergeNfaPair(NGHolder &ga, NGHolder &gb, const ReportManager *rm,
+bool mergeNfaPair(const NGHolder &ga, NGHolder &gb, const ReportManager *rm,
                   const CompileContext &cc) {
     assert(ga.kind == gb.kind);
-    auto a_state_ids = numberStates(ga);
-    auto b_state_ids = numberStates(gb);
 
     // Vacuous NFAs require special checks on their starts to ensure that tops
     // match, and that reports match for mixed-accept cases.
@@ -549,14 +547,13 @@ bool mergeNfaPair(NGHolder &ga, NGHolder &gb, const ReportManager *rm,
         return false;
     }
 
-    u32 cpl = commonPrefixLength(ga, a_state_ids, gb, b_state_ids);
-    if (!shouldMerge(gb, b_state_ids, ga, a_state_ids, cpl, rm, cc)) {
+    u32 cpl = commonPrefixLength(ga, gb);
+    if (!shouldMerge(gb, ga, cpl, rm, cc)) {
         return false;
     }
 
     mergeNfaComponent(gb, ga, cpl);
     reduceImplementableGraph(gb, SOM_NONE, rm, cc);
-    b_state_ids = numberStates(gb);
     return true;
 }
 
index 5f34196146d9e45af8e109d3da32e21b6636239d..ddab88252abe7a02316adc1d7560f5979f04c656 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Intel Corporation
+ * Copyright (c) 2015-2016, Intel Corporation
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -52,10 +52,7 @@ class ReportManager;
  * The CPL is calculated based the topological ordering given by the state
  * indices for each graph.
  */
-u32 commonPrefixLength(const NGHolder &ga,
-                       const ue2::unordered_map<NFAVertex, u32> &a_state_ids,
-                       const NGHolder &gb,
-                       const ue2::unordered_map<NFAVertex, u32> &b_state_ids);
+u32 commonPrefixLength(const NGHolder &ga, const NGHolder &gb);
 
 /**
  * \brief Merge the group of graphs in \p cluster where possible.
@@ -73,7 +70,7 @@ void mergeNfaCluster(const std::vector<NGHolder *> &cluster,
  * Returns false on failure. On success, \p gb is reduced via \ref
  * reduceImplementableGraph and renumbered.
  */
-bool mergeNfaPair(NGHolder &ga, NGHolder &gb, const ReportManager *rm,
+bool mergeNfaPair(const NGHolder &ga, NGHolder &gb, const ReportManager *rm,
                   const CompileContext &cc);
 
 } // namespace ue2
index da9c2438891721c33a896cf04a1255bb92f64a68..71eef7eb9db5c1fbd0f9b281d6ab30d71a05b0d2 100644 (file)
@@ -343,6 +343,47 @@ bool is_virtual_start(NFAVertex v, const NGHolder &g) {
     return g[v].assert_flags & POS_FLAG_VIRTUAL_START;
 }
 
+static
+void reorderSpecials(const NGHolder &g, vector<NFAVertex> &topoOrder) {
+    // Start is last element of reverse topo ordering.
+    auto it = find(topoOrder.begin(), topoOrder.end(), g.start);
+    if (it != topoOrder.end() - 1) {
+        DEBUG_PRINTF("repositioning start\n");
+        assert(it != topoOrder.end());
+        topoOrder.erase(it);
+        topoOrder.insert(topoOrder.end(), g.start);
+    }
+
+    // StartDs is second-to-last element of reverse topo ordering.
+    it = find(topoOrder.begin(), topoOrder.end(), g.startDs);
+    if (it != topoOrder.end() - 2) {
+        DEBUG_PRINTF("repositioning start ds\n");
+        assert(it != topoOrder.end());
+        topoOrder.erase(it);
+        topoOrder.insert(topoOrder.end() - 1, g.startDs);
+    }
+
+    // AcceptEOD is first element of reverse topo ordering.
+    it = find(topoOrder.begin(), topoOrder.end(), g.acceptEod);
+    if (it != topoOrder.begin()) {
+        DEBUG_PRINTF("repositioning accept\n");
+        assert(it != topoOrder.end());
+        topoOrder.erase(it);
+        topoOrder.insert(topoOrder.begin(), g.acceptEod);
+    }
+
+    // Accept is second element of reverse topo ordering, if it's connected.
+    it = find(topoOrder.begin(), topoOrder.end(), g.accept);
+    if (it != topoOrder.begin() + 1) {
+        DEBUG_PRINTF("repositioning accept\n");
+        assert(it != topoOrder.end());
+        topoOrder.erase(it);
+        if (in_degree(g.accept, g) != 0) {
+            topoOrder.insert(topoOrder.begin() + 1, g.accept);
+        }
+    }
+}
+
 vector<NFAVertex> getTopoOrdering(const NGHolder &g) {
     assert(hasCorrectlyNumberedVertices(g));
 
@@ -372,6 +413,8 @@ vector<NFAVertex> getTopoOrdering(const NGHolder &g) {
         color_map(make_iterator_property_map(colour.begin(), index_map))
             .vertex_index_map(index_map));
 
+    reorderSpecials(g, ordering);
+
     return ordering;
 }
 
@@ -629,6 +672,60 @@ unique_ptr<NGHolder> cloneHolder(const NGHolder &in) {
     return h;
 }
 
+void reverseHolder(const NGHolder &g_in, NGHolder &g) {
+    // Make the BGL do the grunt work.
+    ue2::unordered_map<NFAVertex, NFAVertex> vertexMap;
+    boost::transpose_graph(g_in.g, g.g,
+                orig_to_copy(boost::make_assoc_property_map(vertexMap)).
+                vertex_index_map(get(&NFAGraphVertexProps::index, g_in.g)));
+
+    // The transpose_graph operation will have created extra copies of our
+    // specials. We have to rewire their neighbours to the 'real' specials and
+    // delete them.
+    NFAVertex start = vertexMap[g_in.acceptEod];
+    NFAVertex startDs = vertexMap[g_in.accept];
+    NFAVertex accept = vertexMap[g_in.startDs];
+    NFAVertex acceptEod = vertexMap[g_in.start];
+
+    // Successors of starts.
+    for (const auto &e : out_edges_range(start, g)) {
+        NFAVertex v = target(e, g);
+        add_edge(g.start, v, g[e], g);
+    }
+    for (const auto &e : out_edges_range(startDs, g)) {
+        NFAVertex v = target(e, g);
+        add_edge(g.startDs, v, g[e], g);
+    }
+
+    // Predecessors of accepts.
+    for (const auto &e : in_edges_range(accept, g)) {
+        NFAVertex u = source(e, g);
+        add_edge(u, g.accept, g[e], g);
+    }
+    for (const auto &e : in_edges_range(acceptEod, g)) {
+        NFAVertex u = source(e, g);
+        add_edge(u, g.acceptEod, g[e], g);
+    }
+
+    // Remove our impostors.
+    clear_vertex(start, g);
+    remove_vertex(start, g);
+    clear_vertex(startDs, g);
+    remove_vertex(startDs, g);
+    clear_vertex(accept, g);
+    remove_vertex(accept, g);
+    clear_vertex(acceptEod, g);
+    remove_vertex(acceptEod, g);
+
+    // Renumber so that g's properties (number of vertices, edges) are
+    // accurate.
+    g.renumberVertices();
+    g.renumberEdges();
+
+    assert(num_vertices(g) == num_vertices(g_in));
+    assert(num_edges(g) == num_edges(g_in));
+}
+
 #ifndef NDEBUG
 
 bool allMatchStatesHaveReports(const NGHolder &g) {
index 1c6dd4618c8037ff5d50afeb32cc83a19ad207af..6c6907a3a58381ae48ad75e3bba7bc65009bea65 100644 (file)
@@ -174,7 +174,11 @@ bool is_match_vertex(NFAVertex v, const GraphT &g) {
 }
 
 /** Generate a reverse topological ordering for a back-edge filtered version of
- * our graph (as it must be a DAG and correctly numbered) */
+ * our graph (as it must be a DAG and correctly numbered).
+ *
+ * Note: we ensure that we produce a topo ordering that begins with acceptEod
+ * and accept (if present) and ends with startDs followed by start.
+ */
 std::vector<NFAVertex> getTopoOrdering(const NGHolder &g);
 
 /** Comparison functor used to sort by vertex_index. */
@@ -300,6 +304,10 @@ void clearReports(NGHolder &g);
  * r_old. */
 void duplicateReport(NGHolder &g, ReportID r_old, ReportID r_new);
 
+/** Construct a reversed copy of an arbitrary NGHolder, mapping starts to
+ * accepts. */
+void reverseHolder(const NGHolder &g, NGHolder &out);
+
 #ifndef NDEBUG
 
 // Assertions: only available in internal builds.
index 6b19549b1e8184b3b335fdb7c72b6868963b86fc..38c488be31447913a903dcf639a5faf285f8dd60 100644 (file)
@@ -47,6 +47,7 @@
 #include "nfagraph/ng_is_equal.h"
 #include "nfagraph/ng_limex.h"
 #include "nfagraph/ng_mcclellan.h"
+#include "nfagraph/ng_prune.h"
 #include "nfagraph/ng_repeat.h"
 #include "nfagraph/ng_reports.h"
 #include "nfagraph/ng_stop.h"
@@ -788,19 +789,230 @@ void RoseBuildImpl::findTransientLeftfixes(void) {
 
 /** Find all the different roses and their associated literals. */
 static
-map<left_id, vector<RoseVertex>> findLeftSucc(RoseBuildImpl &tbi) {
+map<left_id, vector<RoseVertex>> findLeftSucc(const RoseBuildImpl &build) {
     map<left_id, vector<RoseVertex>> leftfixes;
-    for (auto v : vertices_range(tbi.g)) {
-        if (tbi.g[v].left) {
-            const LeftEngInfo &lei = tbi.g[v].left;
+    for (auto v : vertices_range(build.g)) {
+        if (build.g[v].left) {
+            const LeftEngInfo &lei = build.g[v].left;
             leftfixes[lei].push_back(v);
         }
     }
     return leftfixes;
 }
 
+namespace {
+struct infix_info {
+    set<RoseVertex> preds;
+    set<RoseVertex> succs;
+};
+}
+
 static
-bool triggerKillsRoseGraph(const RoseBuildImpl &tbi, const left_id &left,
+map<NGHolder *, infix_info> findInfixGraphInfo(const RoseBuildImpl &build) {
+    map<NGHolder *, infix_info> rv;
+
+    for (auto v : vertices_range(build.g)) {
+        if (!build.g[v].left) {
+            continue;
+        }
+
+        if (build.isRootSuccessor(v)) {
+            DEBUG_PRINTF("a prefix is never an infix\n");
+            continue;
+        }
+
+        /* ensure only proper nfas */
+        const LeftEngInfo &lei = build.g[v].left;
+        if (!lei.graph) {
+            continue;
+        }
+        if (lei.haig || lei.dfa) {
+            continue;
+        }
+        assert(!lei.castle);
+        infix_info &info = rv[lei.graph.get()];
+        insert(&info.preds, inv_adjacent_vertices_range(v, build.g));
+        info.succs.insert(v);
+    }
+
+    return rv;
+}
+
+static
+map<u32, flat_set<NFAEdge>> getTopInfo(const NGHolder &h) {
+    map<u32, flat_set<NFAEdge>> rv;
+    for (NFAEdge e : out_edges_range(h.start, h)) {
+        for (u32 t : h[e].tops) {
+            rv[t].insert(e);
+        }
+    }
+    return rv;
+}
+
+static
+u32 findUnusedTop(const map<u32, flat_set<NFAEdge>> &tops) {
+    u32 i = 0;
+    while (contains(tops, i)) {
+        i++;
+    }
+    return i;
+}
+
+static
+bool reduceTopTriggerLoad(RoseBuildImpl &build, NGHolder &h, RoseVertex u) {
+    RoseGraph &g = build.g;
+
+    set<u32> tops; /* tops triggered by u */
+    for (RoseEdge e : out_edges_range(u, g)) {
+        RoseVertex v = target(e, g);
+        if (g[v].left.graph.get() != &h) {
+            continue;
+        }
+        tops.insert(g[e].rose_top);
+    }
+
+    assert(!tops.empty());
+    if (tops.size() <= 1) {
+        return false;
+    }
+    DEBUG_PRINTF("%zu triggers %zu tops for %p\n", build.g[u].idx, tops.size(),
+                 &h);
+
+    auto h_top_info = getTopInfo(h);
+    flat_set<NFAEdge> edges_to_trigger;
+    for (u32 t : tops) {
+        insert(&edges_to_trigger, h_top_info[t]);
+    }
+
+    u32 new_top = ~0U;
+    /* check if there is already a top with the right the successor set */
+    for (const auto &elem : h_top_info) {
+        if (elem.second == edges_to_trigger) {
+            new_top = elem.first;
+            break;
+        }
+    }
+
+    /* if no existing suitable top, add a new top for us */
+    if (new_top == ~0U) {
+        new_top = findUnusedTop(h_top_info);
+
+        /* add top to edges out of start */
+        for (NFAEdge e : out_edges_range(h.start, h)) {
+            if (has_intersection(tops, h[e].tops)) {
+                h[e].tops.insert(new_top);
+            }
+        }
+
+        /* check still implementable if we add a new top */
+        if (!isImplementableNFA(h, nullptr, build.cc)) {
+            DEBUG_PRINTF("unable to add new top\n");
+            for (NFAEdge e : out_edges_range(h.start, h)) {
+                h[e].tops.erase(new_top);
+            }
+            /* we should be back to the original graph */
+            assert(isImplementableNFA(h, nullptr, build.cc));
+            return false;
+        }
+    }
+
+    DEBUG_PRINTF("using new merged top %u\n", new_top);
+    assert(new_top != ~0U);
+    for (RoseEdge e: out_edges_range(u, g)) {
+        RoseVertex v = target(e, g);
+        if (g[v].left.graph.get() != &h) {
+            continue;
+        }
+        g[e].rose_top = new_top;
+    }
+
+    return true;
+}
+
+static
+void packInfixTops(NGHolder &h, RoseGraph &g,
+                   const set<RoseVertex> &verts) {
+    if (!is_triggered(h)) {
+        DEBUG_PRINTF("not triggered, no tops\n");
+        return;
+    }
+    assert(isCorrectlyTopped(h));
+    DEBUG_PRINTF("pruning unused tops\n");
+    flat_set<u32> used_tops;
+    for (auto v : verts) {
+        assert(g[v].left.graph.get() == &h);
+
+        for (const auto &e : in_edges_range(v, g)) {
+            u32 top = g[e].rose_top;
+            used_tops.insert(top);
+        }
+    }
+
+    map<u32, u32> top_mapping;
+    for (u32 t : used_tops) {
+        u32 new_top = top_mapping.size();
+        top_mapping[t] = new_top;
+    }
+
+    for (auto v : verts) {
+        assert(g[v].left.graph.get() == &h);
+
+        for (const auto &e : in_edges_range(v, g)) {
+            g[e].rose_top = top_mapping.at(g[e].rose_top);
+        }
+    }
+
+    vector<NFAEdge> dead;
+    for (const auto &e : out_edges_range(h.start, h)) {
+        NFAVertex v = target(e, h);
+        if (v == h.startDs) {
+            continue; // stylised edge, leave it alone.
+        }
+        flat_set<u32> updated_tops;
+        for (u32 t : h[e].tops) {
+            if (contains(top_mapping, t)) {
+                updated_tops.insert(top_mapping.at(t));
+            }
+        }
+        h[e].tops = move(updated_tops);
+        if (h[e].tops.empty()) {
+            DEBUG_PRINTF("edge (start,%u) has only unused tops\n", h[v].index);
+            dead.push_back(e);
+        }
+    }
+
+    if (dead.empty()) {
+        return;
+    }
+
+    remove_edges(dead, h);
+    pruneUseless(h);
+    clearReports(h); // As we may have removed vacuous edges.
+}
+
+static
+void reduceTopTriggerLoad(RoseBuildImpl &build) {
+    auto infixes = findInfixGraphInfo(build);
+
+    for (auto &p : infixes) {
+        if (onlyOneTop(*p.first)) {
+            continue;
+        }
+
+        bool changed = false;
+        for (RoseVertex v : p.second.preds) {
+            changed |= reduceTopTriggerLoad(build, *p.first, v);
+        }
+
+        if (changed) {
+            packInfixTops(*p.first, build.g, p.second.succs);
+            reduceImplementableGraph(*p.first, SOM_NONE, nullptr, build.cc);
+        }
+    }
+}
+
+static
+bool triggerKillsRoseGraph(const RoseBuildImpl &build, const left_id &left,
                            const set<ue2_literal> &all_lits,
                            const RoseEdge &e) {
     assert(left.graph());
@@ -816,8 +1028,8 @@ bool triggerKillsRoseGraph(const RoseBuildImpl &tbi, const left_id &left,
 
     /* check each pred literal to see if they all kill previous graph
      * state */
-    for (u32 lit_id : tbi.g[source(e, tbi.g)].literals) {
-        const rose_literal_id &pred_lit = tbi.literals.right.at(lit_id);
+    for (u32 lit_id : build.g[source(e, build.g)].literals) {
+        const rose_literal_id &pred_lit = build.literals.right.at(lit_id);
         const ue2_literal s = findNonOverlappingTail(all_lits, pred_lit.s);
 
         DEBUG_PRINTF("running graph %zu\n", states.size());
@@ -833,7 +1045,7 @@ bool triggerKillsRoseGraph(const RoseBuildImpl &tbi, const left_id &left,
 }
 
 static
-bool triggerKillsRose(const RoseBuildImpl &tbi, const left_id &left,
+bool triggerKillsRose(const RoseBuildImpl &build, const left_id &left,
                       const set<ue2_literal> &all_lits, const RoseEdge &e) {
     if (left.haig()) {
         /* TODO: To allow this for som-based engines we would also need to
@@ -843,32 +1055,30 @@ bool triggerKillsRose(const RoseBuildImpl &tbi, const left_id &left,
     }
 
     if (left.graph()) {
-        return triggerKillsRoseGraph(tbi, left, all_lits, e);
+        return triggerKillsRoseGraph(build, left, all_lits, e);
     }
 
     if (left.castle()) {
-        return triggerKillsRoseCastle(tbi, left, all_lits, e);
+        return triggerKillsRoseCastle(build, left, all_lits, e);
     }
 
     return false;
 }
 
+/* Sometimes the arrival of a top for a rose infix can ensure that the nfa would
+ * be dead at that time. In the case of multiple trigger literals, we can only
+ * base our decision on that portion of literal after any overlapping literals.
+ */
 static
-void inspectRoseTops(RoseBuildImpl &tbi) {
-    /* Sometimes the arrival of a top for a rose infix can ensure that the nfa
-     * would be dead at that time. In the case of multiple trigger literals we
-     * can only base our decision on that portion of literal after any
-     * overlapping literals */
+void findTopTriggerCancels(RoseBuildImpl &build) {
+    auto left_succ = findLeftSucc(build); /* leftfixes -> succ verts */
 
-    map<left_id, vector<RoseVertex>> roses =
-        findLeftSucc(tbi); /* rose -> succ verts */
-
-    for (const auto &r : roses) {
+    for (const auto &r : left_succ) {
         const left_id &left = r.first;
         const vector<RoseVertex> &succs = r.second;
 
         assert(!succs.empty());
-        if (tbi.isRootSuccessor(*succs.begin())) {
+        if (build.isRootSuccessor(*succs.begin())) {
             /* a prefix is never an infix */
             continue;
         }
@@ -878,10 +1088,10 @@ void inspectRoseTops(RoseBuildImpl &tbi) {
         set<u32> pred_lit_ids;
 
         for (auto v : succs) {
-            for (const auto &e : in_edges_range(v, tbi.g)) {
-                RoseVertex u = source(e, tbi.g);
-                tops_seen.insert(tbi.g[e].rose_top);
-                insert(&pred_lit_ids, tbi.g[u].literals);
+            for (const auto &e : in_edges_range(v, build.g)) {
+                RoseVertex u = source(e, build.g);
+                tops_seen.insert(build.g[e].rose_top);
+                insert(&pred_lit_ids, build.g[u].literals);
                 rose_edges.insert(e);
             }
         }
@@ -893,7 +1103,7 @@ void inspectRoseTops(RoseBuildImpl &tbi) {
         }
 
         for (u32 lit_id : pred_lit_ids) {
-            const rose_literal_id &p_lit = tbi.literals.right.at(lit_id);
+            const rose_literal_id &p_lit = build.literals.right.at(lit_id);
             if (p_lit.delay || p_lit.table == ROSE_ANCHORED) {
                 goto next_rose;
             }
@@ -905,15 +1115,22 @@ void inspectRoseTops(RoseBuildImpl &tbi) {
                      all_lits.size(), rose_edges.size());
 
         for (const auto &e : rose_edges) {
-            if (triggerKillsRose(tbi, left, all_lits, e)) {
+            if (triggerKillsRose(build, left, all_lits, e)) {
                 DEBUG_PRINTF("top will override previous rose state\n");
-                tbi.g[e].rose_cancel_prev_top = true;
+                build.g[e].rose_cancel_prev_top = true;
             }
         }
     next_rose:;
     }
 }
 
+static
+void optimiseRoseTops(RoseBuildImpl &build) {
+    reduceTopTriggerLoad(build);
+    /* prune unused tops ? */
+    findTopTriggerCancels(build);
+}
+
 static
 void buildRoseSquashMasks(RoseBuildImpl &tbi) {
     /* Rose nfa squash masks are applied to the groups when the nfa can no
@@ -1492,7 +1709,7 @@ aligned_unique_ptr<RoseEngine> RoseBuildImpl::buildRose(u32 minWidth) {
 
     /* final prep work */
     remapCastleTops(*this);
-    inspectRoseTops(*this);
+    optimiseRoseTops(*this);
     buildRoseSquashMasks(*this);
 
     rm.assignDkeys(this);
index 01134736bf8fc3c653e429496459e4ecefdc0765..054dd12fdf8ee1845a343f92ff930ae1581edfcc 100644 (file)
@@ -53,7 +53,6 @@
 #include "nfagraph/ng_redundancy.h"
 #include "nfagraph/ng_repeat.h"
 #include "nfagraph/ng_reports.h"
-#include "nfagraph/ng_restructuring.h"
 #include "nfagraph/ng_stop.h"
 #include "nfagraph/ng_uncalc_components.h"
 #include "nfagraph/ng_util.h"
@@ -1457,11 +1456,7 @@ bool hasReformedStartDotStar(const NGHolder &h, const Grey &grey) {
 static
 u32 commonPrefixLength(left_id &r1, left_id &r2) {
     if (r1.graph() && r2.graph()) {
-        auto &g1 = *r1.graph();
-        auto &g2 = *r2.graph();
-        auto state_ids_1 = numberStates(g1);
-        auto state_ids_2 = numberStates(g2);
-        return commonPrefixLength(g1, state_ids_1, g2, state_ids_2);
+        return commonPrefixLength(*r1.graph(), *r2.graph());
     } else if (r1.castle() && r2.castle()) {
         return min(findMinWidth(*r1.castle()), findMinWidth(*r2.castle()));
     }
@@ -1750,7 +1745,6 @@ u32 findUnusedTop(const ue2::flat_set<u32> &tops) {
     while (contains(tops, i)) {
         i++;
     }
-    assert(i < NFA_MAX_TOP_MASKS);
     return i;
 }
 
@@ -1779,11 +1773,6 @@ bool setDistinctTops(NGHolder &h1, const NGHolder &h2,
     DEBUG_PRINTF("before: h1 has %zu tops, h2 has %zu tops\n", tops1.size(),
                  tops2.size());
 
-    if (tops1.size() + tops2.size() > NFA_MAX_TOP_MASKS) {
-        DEBUG_PRINTF("too many tops!\n");
-        return false;
-    }
-
     // If our tops don't intersect, we're OK to merge with no changes.
     if (!has_intersection(tops1, tops2)) {
         DEBUG_PRINTF("tops don't intersect\n");
@@ -1856,11 +1845,6 @@ bool setDistinctSuffixTops(RoseGraph &g, NGHolder &h1, const NGHolder &h2,
     return true;
 }
 
-static
-bool hasMaxTops(const NGHolder &h) {
-    return getTops(h).size() == NFA_MAX_TOP_MASKS;
-}
-
 /** \brief Estimate the number of accel states in the given graph when built as
  * an NFA.
  *
@@ -1899,11 +1883,6 @@ void mergeNfaLeftfixes(RoseBuildImpl &tbi, RoseBouquet &roses) {
                          "with %p (%zu verts)\n",
                          r1.graph(), verts1.size(), r2.graph(), verts2.size());
 
-            if (hasMaxTops(*r1.graph())) {
-                DEBUG_PRINTF("h1 has hit max tops\n");
-                break; // next h1
-            }
-
             u32 accel1 = accel_count[r1];
             if (accel1 >= NFA_MAX_ACCEL_STATES) {
                 DEBUG_PRINTF("h1 has hit max accel\n");
@@ -2203,11 +2182,6 @@ void mergeSuffixes(RoseBuildImpl &tbi, SuffixBouquet &suffixes,
             const deque<RoseVertex> &verts2 = suffixes.vertices(s2);
             assert(s2.graph() && s2.graph()->kind == NFA_SUFFIX);
 
-            if (hasMaxTops(*s1.graph())) {
-                DEBUG_PRINTF("h1 has hit max tops\n");
-                break; // next h1
-            }
-
             if (!acyclic) {
                 u32 accel1 = accel_count[s1];
                 if (accel1 >= NFA_MAX_ACCEL_STATES) {