rose: add multi-path shufti 16x8, 32x8, 32x16, 64x8 and multi-path lookaround instructions.

This commit is contained in:
Xu, Chi
2017-03-31 04:37:33 +08:00
committed by Matthew Barr
parent 7533e3341e
commit ae3cb7de6f
12 changed files with 2133 additions and 279 deletions

View File

@@ -45,6 +45,7 @@
#include <cstdlib>
#include <queue>
#include <sstream>
using namespace std;
@@ -62,6 +63,20 @@ static const u32 MAX_LOOKAROUND_ENTRIES = 16;
/** \brief We would rather have lookarounds with smaller reach than this. */
static const u32 LOOKAROUND_WIDE_REACH = 200;
#if defined(DEBUG) || defined(DUMP_SUPPORT)
static UNUSED
string dump(const map<s32, CharReach> &look) {
ostringstream oss;
for (auto it = look.begin(), ite = look.end(); it != ite; ++it) {
if (it != look.begin()) {
oss << ", ";
}
oss << "{" << it->first << ": " << describeClass(it->second) << "}";
}
return oss.str();
}
#endif
static
void getForwardReach(const NGHolder &g, u32 top, map<s32, CharReach> &look) {
ue2::flat_set<NFAVertex> curr, next;
@@ -298,21 +313,6 @@ void findBackwardReach(const RoseGraph &g, const RoseVertex v,
// TODO: implement DFA variants if necessary.
}
#if defined(DEBUG) || defined(DUMP_SUPPORT)
#include <sstream>
static UNUSED
string dump(const map<s32, CharReach> &look) {
ostringstream oss;
for (auto it = look.begin(), ite = look.end(); it != ite; ++it) {
if (it != look.begin()) {
oss << ", ";
}
oss << "{" << it->first << ": " << describeClass(it->second) << "}";
}
return oss.str();
}
#endif
static
void normalise(map<s32, CharReach> &look) {
// We can erase entries where the reach is "all characters".
@@ -554,6 +554,76 @@ void trimLiterals(const RoseBuildImpl &build, const RoseVertex v,
DEBUG_PRINTF("post-trim lookaround: %s\n", dump(look).c_str());
}
static
void normaliseLeftfix(map<s32, CharReach> &look) {
// We can erase entries where the reach is "all characters", except for the
// very first one -- this might be required to establish a minimum bound on
// the literal's match offset.
// TODO: It would be cleaner to use a literal program instruction to check
// the minimum bound explicitly.
if (look.empty()) {
return;
}
const auto earliest = begin(look)->first;
vector<s32> dead;
for (const auto &m : look) {
if (m.second.all() && m.first != earliest) {
dead.push_back(m.first);
}
}
erase_all(&look, dead);
}
static
bool trimMultipathLeftfix(const RoseBuildImpl &build, const RoseVertex v,
vector<map<s32, CharReach>> &looks) {
size_t path_count = 0;
for (auto &look : looks) {
++path_count;
DEBUG_PRINTF("Path #%ld\n", path_count);
assert(!look.empty());
trimLiterals(build, v, look);
if (look.empty()) {
return false;
}
// Could be optimized here, just keep the empty byte of the longest path
normaliseLeftfix(look);
if (look.size() > MAX_LOOKAROUND_ENTRIES) {
DEBUG_PRINTF("lookaround too big (%zu entries)\n", look.size());
return false;
}
}
return true;
}
static
void transToLookaround(const vector<map<s32, CharReach>> &looks,
vector<vector<LookEntry>> &lookarounds) {
for (const auto &look : looks) {
vector<LookEntry> lookaround;
DEBUG_PRINTF("lookaround: %s\n", dump(look).c_str());
lookaround.reserve(look.size());
for (const auto &m : look) {
if (m.first < -128 || m.first > 127) {
DEBUG_PRINTF("range too big\n");
lookarounds.clear();
return;
}
s8 offset = verify_s8(m.first);
lookaround.emplace_back(offset, m.second);
}
lookarounds.push_back(lookaround);
}
}
void findLookaroundMasks(const RoseBuildImpl &tbi, const RoseVertex v,
vector<LookEntry> &lookaround) {
lookaround.clear();
@@ -592,115 +662,155 @@ void findLookaroundMasks(const RoseBuildImpl &tbi, const RoseVertex v,
}
static
bool hasSingleFloatingStart(const NGHolder &g) {
NFAVertex initial = NGHolder::null_vertex();
for (auto v : adjacent_vertices_range(g.startDs, g)) {
if (v == g.startDs) {
continue;
}
if (initial != NGHolder::null_vertex()) {
DEBUG_PRINTF("more than one start\n");
return false;
}
initial = v;
}
bool checkShuftiBuckets(const vector<map<s32, CharReach>> &looks,
u32 bucket_size) {
set<u32> bucket;
for (const auto &look : looks) {
for (const auto &l : look) {
CharReach cr = l.second;
if (cr.count() > 128) {
cr.flip();
}
map <u16, u16> lo2hi;
if (initial == NGHolder::null_vertex()) {
DEBUG_PRINTF("no floating starts\n");
return false;
}
for (size_t i = cr.find_first(); i != CharReach::npos;) {
u8 it_hi = i >> 4;
u16 low_encode = 0;
while (i != CharReach::npos && (i >> 4) == it_hi) {
low_encode |= 1 << (i &0xf);
i = cr.find_next(i);
}
lo2hi[low_encode] |= 1 << it_hi;
}
// Anchored start must have no successors other than startDs and initial.
for (auto v : adjacent_vertices_range(g.start, g)) {
if (v != initial && v != g.startDs) {
DEBUG_PRINTF("anchored start\n");
return false;
for (const auto &it : lo2hi) {
u32 hi_lo = (it.second << 16) | it.first;
bucket.insert(hi_lo);
}
}
}
return true;
DEBUG_PRINTF("shufti has %lu bucket(s)\n", bucket.size());
return bucket.size() <= bucket_size;
}
static
bool getTransientPrefixReach(const NGHolder &g, u32 lag,
map<s32, CharReach> &look) {
if (in_degree(g.accept, g) != 1) {
DEBUG_PRINTF("more than one accept\n");
bool getTransientPrefixReach(const NGHolder &g, ReportID report, u32 lag,
vector<map<s32, CharReach>> &looks) {
if (!isAcyclic(g)) {
DEBUG_PRINTF("contains back-edge\n");
return false;
}
// Must be a floating chain wired to startDs.
if (!hasSingleFloatingStart(g)) {
DEBUG_PRINTF("not a single floating start\n");
// Must be floating chains wired to startDs.
if (!isFloating(g)) {
DEBUG_PRINTF("not a floating start\n");
return false;
}
NFAVertex v = *(inv_adjacent_vertices(g.accept, g).first);
u32 i = lag + 1;
while (v != g.startDs) {
DEBUG_PRINTF("i=%u, v=%zu\n", i, g[v].index);
if (is_special(v, g)) {
DEBUG_PRINTF("special\n");
vector<NFAVertex> curr;
for (auto v : inv_adjacent_vertices_range(g.accept, g)) {
if (v == g.start || v == g.startDs) {
DEBUG_PRINTF("empty graph\n");
return true;
}
if (contains(g[v].reports, report)) {
curr.push_back(v);
}
}
assert(!curr.empty());
u32 total_len = curr.size();
for (const auto &v : curr) {
looks.emplace_back(map<s32, CharReach>());
looks.back()[0 - (lag + 1)] = g[v].char_reach;
}
bool curr_active = false;
/* For each offset -i, we backwardly trace the path by vertices in curr.
* Once there are more than 8 paths and more than 64 bits total_len,
* which means that neither MULTIPATH_LOOKAROUND nor MULTIPATH_SHUFTI
* could be successfully built, we will give up the path finding.
* Otherwise, the loop will halt when all vertices in curr are startDs.
*/
for (u32 i = lag + 2; i < (lag + 2) + MAX_BACK_LEN; i++) {
curr_active = false;
size_t curr_size = curr.size();
if (curr.size() > 1 && i > lag + MULTIPATH_MAX_LEN) {
DEBUG_PRINTF("range is larger than 16 in multi-path\n");
return false;
}
look[0 - i] = g[v].char_reach;
NFAVertex next = NGHolder::null_vertex();
for (auto u : inv_adjacent_vertices_range(v, g)) {
if (u == g.start) {
continue; // Benign, checked by hasSingleFloatingStart
}
if (next == NGHolder::null_vertex()) {
next = u;
for (size_t idx = 0; idx < curr_size; idx++) {
NFAVertex v = curr[idx];
if (v == g.startDs) {
continue;
}
DEBUG_PRINTF("branch\n");
return false;
}
assert(!is_special(v, g));
if (next == NGHolder::null_vertex() || next == v) {
DEBUG_PRINTF("no predecessor or only self-loop\n");
// This graph is malformed -- all vertices in a graph that makes it
// to this analysis should have predecessors.
assert(0);
return false;
}
for (auto u : inv_adjacent_vertices_range(v, g)) {
if (u == g.start || u == g.startDs) {
curr[idx] = g.startDs;
break;
}
}
v = next;
i++;
if (is_special(curr[idx], g)) {
continue;
}
for (auto u : inv_adjacent_vertices_range(v, g)) {
curr_active = true;
if (curr[idx] == v) {
curr[idx] = u;
looks[idx][0 - i] = g[u].char_reach;
total_len++;
} else {
curr.push_back(u);
looks.push_back(looks[idx]);
(looks.back())[0 - i] = g[u].char_reach;
total_len += looks.back().size();
}
if (curr.size() > MAX_LOOKAROUND_PATHS && total_len > 64) {
DEBUG_PRINTF("too many branches\n");
return false;
}
}
}
if (!curr_active) {
break;
}
}
if (curr_active) {
DEBUG_PRINTF("single path too long\n");
return false;
}
// More than 8 paths, check multi-path shufti.
if (curr.size() > MAX_LOOKAROUND_PATHS) {
u32 bucket_size = total_len > 32 ? 8 : 16;
if (!checkShuftiBuckets(looks, bucket_size)) {
DEBUG_PRINTF("shufti has too many buckets\n");
return false;
}
}
assert(!looks.empty());
if (looks.size() == 1) {
DEBUG_PRINTF("single lookaround\n");
} else {
DEBUG_PRINTF("multi-path lookaround\n");
}
DEBUG_PRINTF("done\n");
return true;
}
static
void normaliseLeftfix(map<s32, CharReach> &look) {
// We can erase entries where the reach is "all characters", except for the
// very first one -- this might be required to establish a minimum bound on
// the literal's match offset.
// TODO: It would be cleaner to use a literal program instruction to check
// the minimum bound explicitly.
if (look.empty()) {
return;
}
const auto earliest = begin(look)->first;
vector<s32> dead;
for (const auto &m : look) {
if (m.second.all() && m.first != earliest) {
dead.push_back(m.first);
}
}
erase_all(&look, dead);
}
bool makeLeftfixLookaround(const RoseBuildImpl &build, const RoseVertex v,
vector<LookEntry> &lookaround) {
vector<vector<LookEntry>> &lookaround) {
lookaround.clear();
const RoseGraph &g = build.g;
@@ -716,36 +826,19 @@ bool makeLeftfixLookaround(const RoseBuildImpl &build, const RoseVertex v,
return false;
}
map<s32, CharReach> look;
if (!getTransientPrefixReach(*leftfix.graph(), g[v].left.lag, look)) {
DEBUG_PRINTF("not a chain\n");
vector<map<s32, CharReach>> looks;
if (!getTransientPrefixReach(*leftfix.graph(), g[v].left.leftfix_report,
g[v].left.lag, looks)) {
DEBUG_PRINTF("graph has loop or too large\n");
return false;
}
trimLiterals(build, v, look);
normaliseLeftfix(look);
if (look.size() > MAX_LOOKAROUND_ENTRIES) {
DEBUG_PRINTF("lookaround too big (%zu entries)\n", look.size());
if (!trimMultipathLeftfix(build, v, looks)) {
return false;
}
transToLookaround(looks, lookaround);
if (look.empty()) {
DEBUG_PRINTF("lookaround empty; this is weird\n");
return false;
}
lookaround.reserve(look.size());
for (const auto &m : look) {
if (m.first < -128 || m.first > 127) {
DEBUG_PRINTF("range too big\n");
return false;
}
s8 offset = verify_s8(m.first);
lookaround.emplace_back(offset, m.second);
}
return true;
return !lookaround.empty();
}
void mergeLookaround(vector<LookEntry> &lookaround,