#include "config.h"
#include "ng_squash.h"
#include "ng_dominators.h"
#include "ng_dump.h"
#include "ng_holder.h"
#include "ng_prune.h"
#include "ng_region.h"
#include "ng_som_util.h"
#include "ng_util.h"
#include "util/container.h"
#include "util/graph_range.h"
#include "util/report_manager.h"
#include "ue2common.h"
#include <deque>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <boost/graph/depth_first_search.hpp>
#include <boost/graph/reverse_graph.hpp>
using namespace std;
namespace ue2 {
using PostDomTree = unordered_map<NFAVertex, unordered_set<NFAVertex>>;
static
PostDomTree buildPDomTree(const NGHolder &g) {
PostDomTree tree;
tree.reserve(num_vertices(g));
auto postdominators = findPostDominators(g);
for (auto v : vertices_range(g)) {
if (is_special(v, g)) {
continue;
}
NFAVertex pdom = postdominators[v];
if (pdom) {
DEBUG_PRINTF("vertex %zu -> %zu\n", g[pdom].index, g[v].index);
tree[pdom].insert(v);
}
}
return tree;
}
static
void buildSquashMask(NFAStateSet &mask, const NGHolder &g, NFAVertex v,
const CharReach &cr, const NFAStateSet &init,
const vector<NFAVertex> &vByIndex, const PostDomTree &tree,
som_type som, const vector<DepthMinMax> &som_depths,
const unordered_map<NFAVertex, u32> ®ion_map,
smgb_cache &cache) {
DEBUG_PRINTF("build base squash mask for vertex %zu)\n", g[v].index);
vector<NFAVertex> q;
auto it = tree.find(v);
if (it != tree.end()) {
q.insert(q.end(), it->second.begin(), it->second.end());
}
const u32 v_index = g[v].index;
while (!q.empty()) {
NFAVertex u = q.back();
q.pop_back();
const CharReach &cru = g[u].char_reach;
if ((cru & ~cr).any()) {
mask.set();
return;
}
const u32 u_index = g[u].index;
if (som) {
const depth &max_som_dist_u = som_depths[u_index].max;
const depth &min_som_dist_v = som_depths[v_index].min;
if (max_som_dist_u.is_infinite()) {
if (mustBeSetBefore(u, v, g, cache)
&& !somMayGoBackwards(u, g, region_map, cache)) {
DEBUG_PRINTF("u %u v %u\n", u_index, v_index);
goto squash_ok;
}
}
if (max_som_dist_u > min_som_dist_v) {
goto add_children_to_queue;
}
}
squash_ok:
mask.set(u_index);
DEBUG_PRINTF("pdom'ed %u\n", u_index);
add_children_to_queue:
it = tree.find(u);
if (it != tree.end()) {
q.insert(q.end(), it->second.begin(), it->second.end());
}
}
if (cr.all()) {
for (size_t i = init.find_first(); i != init.npos;
i = init.find_next(i)) {
NFAVertex iv = vByIndex[i];
for (auto w : adjacent_vertices_range(iv, g)) {
if (w == g.accept || w == g.acceptEod) {
DEBUG_PRINTF("skipping %zu due to vacuous accept\n", i);
goto next_init_state;
}
u32 vert_id = g[w].index;
if (w != iv && w != v && !mask.test(vert_id)) {
DEBUG_PRINTF("skipping %zu due to %u\n", i, vert_id);
goto next_init_state;
}
}
DEBUG_PRINTF("pdom'ed %zu\n", i);
mask.set(i);
next_init_state:;
}
}
mask.flip();
}
static
void buildSucc(NFAStateSet &succ, const NGHolder &g, NFAVertex v) {
for (auto w : adjacent_vertices_range(v, g)) {
if (!is_special(w, g)) {
succ.set(g[w].index);
}
}
}
static
void buildPred(NFAStateSet &pred, const NGHolder &g, NFAVertex v) {
for (auto u : inv_adjacent_vertices_range(v, g)) {
if (!is_special(u, g)) {
pred.set(g[u].index);
}
}
}
static
void findDerivedSquashers(const NGHolder &g, const vector<NFAVertex> &vByIndex,
const PostDomTree &pdom_tree, const NFAStateSet &init,
unordered_map<NFAVertex, NFAStateSet> *squash,
som_type som, const vector<DepthMinMax> &som_depths,
const unordered_map<NFAVertex, u32> ®ion_map,
smgb_cache &cache) {
deque<NFAVertex> remaining;
for (const auto &m : *squash) {
remaining.push_back(m.first);
}
while (!remaining.empty()) {
NFAVertex v = remaining.back();
remaining.pop_back();
for (auto u : inv_adjacent_vertices_range(v, g)) {
if (is_special(u, g)) {
continue;
}
if (g[v].char_reach != g[u].char_reach) {
continue;
}
if (out_degree(u, g) != 1) {
continue;
}
NFAStateSet u_squash(init.size());
size_t u_index = g[u].index;
buildSquashMask(u_squash, g, u, g[u].char_reach, init, vByIndex,
pdom_tree, som, som_depths, region_map, cache);
u_squash.set(u_index);
if ((~u_squash).any()) { DEBUG_PRINTF("%zu is an upstream squasher of %zu\n", u_index,
g[v].index);
(*squash)[u] = u_squash;
remaining.push_back(u);
}
}
}
}
static
void clearMutualSquashers(const NGHolder &g, const vector<NFAVertex> &vByIndex,
unordered_map<NFAVertex, NFAStateSet> &squash) {
for (auto it = squash.begin(); it != squash.end();) {
NFAVertex a = it->first;
u32 a_index = g[a].index;
NFAStateSet a_squash = ~it->second;
for (auto b_index = a_squash.find_first(); b_index != a_squash.npos;
b_index = a_squash.find_next(b_index)) {
assert(b_index != a_index);
NFAVertex b = vByIndex[b_index];
auto b_it = squash.find(b);
if (b_it == squash.end()) {
continue;
}
auto &b_squash = b_it->second;
if (!b_squash.test(a_index)) {
DEBUG_PRINTF("removing mutual squash %u %zu\n",
a_index, b_index);
b_squash.set(a_index);
it->second.set(b_index);
}
}
if (it->second.all()) {
DEBUG_PRINTF("%u is no longer an effective squash state\n",
a_index);
it = squash.erase(it);
} else {
++it;
}
}
}
unordered_map<NFAVertex, NFAStateSet> findSquashers(const NGHolder &g,
som_type som) {
unordered_map<NFAVertex, NFAStateSet> squash;
const u32 numStates = num_vertices(g);
auto pdom_tree = buildPDomTree(g);
vector<NFAVertex> vByIndex(numStates, NGHolder::null_vertex());
NFAStateSet initStates(numStates);
smgb_cache cache(g);
unordered_map<NFAVertex, u32> region_map;
vector<DepthMinMax> som_depths;
if (som) {
region_map = assignRegions(g);
som_depths = getDistancesFromSOM(g);
}
for (auto v : vertices_range(g)) {
const u32 vert_id = g[v].index;
DEBUG_PRINTF("vertex %u/%u\n", vert_id, numStates);
assert(vert_id < numStates);
vByIndex[vert_id] = v;
if (is_any_start(v, g) || !in_degree(v, g)) {
initStates.set(vert_id);
}
}
for (u32 i = 0; i < numStates; i++) {
NFAVertex v = vByIndex[i];
assert(v != NGHolder::null_vertex());
const CharReach &cr = g[v].char_reach;
if (!hasSelfLoop(v, g) || initStates.test(i)) {
continue;
}
DEBUG_PRINTF("state %u is cyclic\n", i);
NFAStateSet mask(numStates), succ(numStates), pred(numStates);
buildSquashMask(mask, g, v, cr, initStates, vByIndex, pdom_tree, som,
som_depths, region_map, cache);
buildSucc(succ, g, v);
buildPred(pred, g, v);
const auto &reports = g[v].reports;
for (size_t j = succ.find_first(); j != succ.npos;
j = succ.find_next(j)) {
NFAVertex vj = vByIndex[j];
NFAStateSet pred2(numStates);
buildPred(pred2, g, vj);
if (pred2 == pred) {
DEBUG_PRINTF("adding the sm from %zu to %u's sm\n", j, i);
NFAStateSet tmp(numStates);
buildSquashMask(tmp, g, vj, cr, initStates, vByIndex, pdom_tree,
som, som_depths, region_map, cache);
mask &= tmp;
}
}
for (size_t j = pred.find_first(); j != pred.npos;
j = pred.find_next(j)) {
NFAVertex vj = vByIndex[j];
NFAStateSet succ2(numStates);
buildSucc(succ2, g, vj);
if ((succ2 & ~succ).any()) {
continue;
}
if (som) {
const depth &max_som_dist_j =
som_depths[g[vj].index].max;
const depth &min_som_dist_v =
som_depths[g[v].index].min;
if (max_som_dist_j > min_som_dist_v ||
max_som_dist_j.is_infinite()) {
continue;
}
}
const CharReach &crv = g[vj].char_reach;
bool seen_special = false;
for (auto w : adjacent_vertices_range(vj, g)) {
if (is_special(w, g)) {
if (!edge(v, w, g).second) {
goto next_j;
}
seen_special = true;
}
}
if (seen_special && g[vj].reports != reports) {
continue;
}
if ((crv & ~cr).none()) {
NFAStateSet tmp(numStates);
buildSquashMask(tmp, g, vj, cr, initStates, vByIndex, pdom_tree,
som, som_depths, region_map, cache);
mask &= tmp;
mask.reset(j);
}
next_j:;
}
mask.set(i);
if ((~mask).any()) { DEBUG_PRINTF("%u squashes %zu other states\n", i, (~mask).count());
squash.emplace(v, mask);
}
}
findDerivedSquashers(g, vByIndex, pdom_tree, initStates, &squash, som,
som_depths, region_map, cache);
clearMutualSquashers(g, vByIndex, squash);
return squash;
}
#define MIN_PURE_ACYCLIC_SQUASH 10
void filterSquashers(const NGHolder &g,
unordered_map<NFAVertex, NFAStateSet> &squash) {
assert(hasCorrectlyNumberedVertices(g));
DEBUG_PRINTF("filtering\n");
vector<NFAVertex> rev(num_vertices(g));
for (auto v : vertices_range(g)) {
rev[g[v].index] = v;
}
for (auto v : vertices_range(g)) {
if (!contains(squash, v)) {
continue;
}
DEBUG_PRINTF("looking at squash set for vertex %zu\n", g[v].index);
if (!hasSelfLoop(v, g)) {
DEBUG_PRINTF("acyclic\n");
squash.erase(v);
continue;
}
NFAStateSet squashed = squash[v];
squashed.flip();
for (auto sq = squashed.find_first(); sq != squashed.npos;
sq = squashed.find_next(sq)) {
NFAVertex u = rev[sq];
if (hasSelfLoop(u, g)) {
DEBUG_PRINTF("squashing a cyclic (%zu) is always good\n", sq);
goto next_vertex;
}
}
if (squashed.count() < MIN_PURE_ACYCLIC_SQUASH) {
DEBUG_PRINTF("squash set too small\n");
squash.erase(v);
continue;
}
next_vertex:;
DEBUG_PRINTF("squash set ok\n");
}
}
static
void getHighlanderReporters(const NGHolder &g, const NFAVertex accept,
const ReportManager &rm,
set<NFAVertex> &verts) {
for (auto v : inv_adjacent_vertices_range(accept, g)) {
if (v == g.accept) {
continue;
}
const auto &reports = g[v].reports;
if (reports.empty()) {
assert(0);
continue;
}
for (auto report : reports) {
const Report &ir = rm.getReport(report);
if (ir.ekey == INVALID_EKEY || ir.type != EXTERNAL_CALLBACK) {
goto next_vertex;
}
if (ir.hasBounds()) {
goto next_vertex;
}
}
verts.insert(v);
next_vertex:
continue;
}
}
static
void removeEdgesToAccept(NGHolder &g, NFAVertex v) {
const auto &reports = g[v].reports;
assert(!reports.empty());
set<NFAEdge> dead;
for (const auto &e : in_edges_range(g.accept, g)) {
NFAVertex u = source(e, g);
const auto &r = g[u].reports;
if (!r.empty() && is_subset_of(r, reports)) {
DEBUG_PRINTF("vertex %zu\n", g[u].index);
dead.insert(e);
}
}
for (const auto &e : in_edges_range(g.acceptEod, g)) {
NFAVertex u = source(e, g);
const auto &r = g[u].reports;
if (!r.empty() && is_subset_of(r, reports)) {
DEBUG_PRINTF("vertex %zu\n", g[u].index);
dead.insert(e);
}
}
assert(!dead.empty());
remove_edges(dead, g);
}
static
vector<NFAVertex> findUnreachable(const NGHolder &g) {
const boost::reverse_graph<NGHolder, const NGHolder &> revg(g);
unordered_map<NFAVertex, boost::default_color_type> colours;
colours.reserve(num_vertices(g));
depth_first_visit(revg, g.acceptEod,
make_dfs_visitor(boost::null_visitor()),
make_assoc_property_map(colours));
vector<NFAVertex> unreach;
for (auto v : vertices_range(revg)) {
if (!contains(colours, v)) {
unreach.push_back(NFAVertex(v));
}
}
return unreach;
}
unordered_map<NFAVertex, NFAStateSet>
findHighlanderSquashers(const NGHolder &g, const ReportManager &rm) {
unordered_map<NFAVertex, NFAStateSet> squash;
set<NFAVertex> verts;
getHighlanderReporters(g, g.accept, rm, verts);
getHighlanderReporters(g, g.acceptEod, rm, verts);
if (verts.empty()) {
DEBUG_PRINTF("no highlander reports\n");
return squash;
}
const u32 numStates = num_vertices(g);
for (auto v : verts) {
DEBUG_PRINTF("vertex %zu with %zu reports\n", g[v].index,
g[v].reports.size());
unordered_map<NFAVertex, NFAVertex> orig_to_copy;
NGHolder h;
cloneHolder(h, g, &orig_to_copy);
removeEdgesToAccept(h, orig_to_copy[v]);
vector<NFAVertex> unreach = findUnreachable(h);
DEBUG_PRINTF("can squash %zu vertices\n", unreach.size());
if (unreach.empty()) {
continue;
}
if (!contains(squash, v)) {
squash[v] = NFAStateSet(numStates);
squash[v].set();
}
NFAStateSet &mask = squash[v];
for (auto uv : unreach) {
DEBUG_PRINTF("squashes index %zu\n", h[uv].index);
mask.reset(h[uv].index);
}
}
return squash;
}
}