#include "teddy_compile.h"
#include "fdr.h"
#include "fdr_internal.h"
#include "fdr_compile_internal.h"
#include "fdr_confirm.h"
#include "fdr_engine_description.h"
#include "teddy_internal.h"
#include "teddy_engine_description.h"
#include "grey.h"
#include "ue2common.h"
#include "hwlm/hwlm_build.h"
#include "util/alloc.h"
#include "util/compare.h"
#include "util/container.h"
#include "util/make_unique.h"
#include "util/noncopyable.h"
#include "util/popcount.h"
#include "util/small_vector.h"
#include "util/target_info.h"
#include "util/verify_types.h"
#include <algorithm>
#include <cassert>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>
using namespace std;
namespace ue2 {
namespace {
static constexpr size_t MAX_NUM_MASKS = 4;
class TeddyCompiler : noncopyable {
const TeddyEngineDescription ŋ
const Grey &grey;
const vector<hwlmLiteral> &lits;
map<BucketIndex, std::vector<LiteralIndex>> bucketToLits;
bool make_small;
public:
TeddyCompiler(const vector<hwlmLiteral> &lits_in,
map<BucketIndex, std::vector<LiteralIndex>> bucketToLits_in,
const TeddyEngineDescription &eng_in, bool make_small_in,
const Grey &grey_in)
: eng(eng_in), grey(grey_in), lits(lits_in),
bucketToLits(move(bucketToLits_in)), make_small(make_small_in) {}
bytecode_ptr<FDR> build();
};
class TeddySet {
static constexpr size_t LITS_PER_SET = 20;
u32 len;
small_vector<u16, MAX_NUM_MASKS * 2> nibbleSets;
small_vector<u32, LITS_PER_SET> litIds;
public:
explicit TeddySet(u32 len_in) : len(len_in), nibbleSets(len_in * 2, 0) {}
size_t litCount() const { return litIds.size(); }
const small_vector<u32, LITS_PER_SET> &getLits() const { return litIds; }
bool operator<(const TeddySet &s) const {
return litIds < s.litIds;
}
#ifdef TEDDY_DEBUG
void dump() const {
printf("TS: ");
for (u32 i = 0; i < nibbleSets.size(); i++) {
printf("%04x ", (u32)nibbleSets[i]);
}
printf("\nnlits: %zu\nLit ids: ", litCount());
printf("Prob: %llu\n", probability());
for (const auto &id : litIds) {
printf("%u ", id);
}
printf("\n");
printf("Flood prone : %s\n", isRunProne() ? "yes" : "no");
}
#endif
bool identicalTail(const TeddySet &ts) const {
return nibbleSets == ts.nibbleSets;
}
void addLiteral(u32 lit_id, const hwlmLiteral &lit) {
const string &s = lit.s;
for (u32 i = 0; i < len; i++) {
if (i < s.size()) {
u8 c = s[s.size() - i - 1];
u8 c_hi = (c >> 4) & 0xf;
u8 c_lo = c & 0xf;
nibbleSets[i * 2] = 1 << c_lo;
if (lit.nocase && ourisalpha(c)) {
nibbleSets[i * 2 + 1] =
(1 << (c_hi & 0xd)) | (1 << (c_hi | 0x2));
} else {
nibbleSets[i * 2 + 1] = 1 << c_hi;
}
} else {
nibbleSets[i * 2] = nibbleSets[i * 2 + 1] = 0xffff;
}
}
litIds.push_back(lit_id);
sort_and_unique(litIds);
}
u64a probability() const {
u64a val = 1;
for (size_t i = 0; i < nibbleSets.size(); i++) {
val *= popcount32((u32)nibbleSets[i]);
}
return val;
}
u64a heuristic() const {
return probability() * (2 + litCount());
}
bool isRunProne() const {
u16 lo_and = 0xffff;
u16 hi_and = 0xffff;
for (u32 i = 0; i < len; i++) {
lo_and &= nibbleSets[i * 2];
hi_and &= nibbleSets[i * 2 + 1];
}
if (!lo_and || !hi_and) {
return false;
}
return true;
}
friend TeddySet merge(const TeddySet &a, const TeddySet &b) {
assert(a.nibbleSets.size() == b.nibbleSets.size());
TeddySet m(a);
for (size_t i = 0; i < m.nibbleSets.size(); i++) {
m.nibbleSets[i] |= b.nibbleSets[i];
}
m.litIds.insert(m.litIds.end(), b.litIds.begin(), b.litIds.end());
sort_and_unique(m.litIds);
return m;
}
};
static
bool pack(const vector<hwlmLiteral> &lits,
const TeddyEngineDescription &eng,
map<BucketIndex, std::vector<LiteralIndex>> &bucketToLits) {
set<TeddySet> sts;
for (u32 i = 0; i < lits.size(); i++) {
TeddySet ts(eng.numMasks);
ts.addLiteral(i, lits[i]);
sts.insert(ts);
}
while (1) {
#ifdef TEDDY_DEBUG
printf("Size %zu\n", sts.size());
for (const TeddySet &ts : sts) {
printf("\n");
ts.dump();
}
printf("\n===============================================\n");
#endif
auto m1 = sts.end(), m2 = sts.end();
u64a best = 0xffffffffffffffffULL;
for (auto i1 = sts.begin(), e1 = sts.end(); i1 != e1; ++i1) {
const TeddySet &s1 = *i1;
for (auto i2 = next(i1), e2 = sts.end(); i2 != e2; ++i2) {
const TeddySet &s2 = *i2;
if ((sts.size() <= eng.getNumBuckets()) &&
!s1.identicalTail(s2)) {
continue;
}
TeddySet tmpSet = merge(s1, s2);
u64a newScore = tmpSet.heuristic();
u64a oldScore = s1.heuristic() + s2.heuristic();
if (newScore < oldScore) {
m1 = i1;
m2 = i2;
break;
} else {
u64a score = newScore - oldScore;
bool oldRunProne = s1.isRunProne() && s2.isRunProne();
bool newRunProne = tmpSet.isRunProne();
if (newRunProne && !oldRunProne) {
continue;
}
if (score < best) {
best = score;
m1 = i1;
m2 = i2;
}
}
}
}
if ((m1 == sts.end()) || (m2 == sts.end())) {
break;
}
TeddySet nts = merge(*m1, *m2);
#ifdef TEDDY_DEBUG
printf("Merging\n");
printf("m1 = \n");
m1->dump();
printf("m2 = \n");
m2->dump();
printf("nts = \n");
nts.dump();
printf("\n===============================================\n");
#endif
sts.erase(m1);
sts.erase(m2);
sts.insert(nts);
}
if (sts.size() > eng.getNumBuckets()) {
return false;
}
u32 bucket_id = 0;
for (const TeddySet &ts : sts) {
const auto &ts_lits = ts.getLits();
auto &bucket_lits = bucketToLits[bucket_id];
bucket_lits.insert(end(bucket_lits), begin(ts_lits), end(ts_lits));
bucket_id++;
}
return true;
}
#define NO_REINFORCEMENT N_CHARS
#define ALL_CHAR_SET N_CHARS
#define REINFORCED_MSK_LEN 8
#define RTABLE_SIZE ((N_CHARS + 1) * REINFORCED_MSK_LEN)
static
void initReinforcedTable(u8 *rmsk) {
u64a *mask = (u64a *)rmsk;
fill_n(mask, N_CHARS, 0x00ffffffffffffffULL);
}
static
void fillReinforcedMskZero(u8 *rmsk) {
u8 *mc = rmsk + NO_REINFORCEMENT * REINFORCED_MSK_LEN;
fill_n(mc, REINFORCED_MSK_LEN, 0x00);
}
static
void fillReinforcedMsk(u8 *rmsk, u16 c, u32 j, u8 bmsk) {
assert(j > 0);
if (c == ALL_CHAR_SET) {
for (size_t i = 0; i < N_CHARS; i++) {
u8 *mc = rmsk + i * REINFORCED_MSK_LEN;
mc[j - 1] &= ~bmsk;
}
} else {
u8 *mc = rmsk + c * REINFORCED_MSK_LEN;
mc[j - 1] &= ~bmsk;
}
}
static
void fillDupNibbleMasks(const map<BucketIndex,
vector<LiteralIndex>> &bucketToLits,
const vector<hwlmLiteral> &lits,
u32 numMasks, size_t maskLen,
u8 *baseMsk) {
u32 maskWidth = 2;
memset(baseMsk, 0xff, maskLen);
for (const auto &b2l : bucketToLits) {
const u32 &bucket_id = b2l.first;
const vector<LiteralIndex> &ids = b2l.second;
const u8 bmsk = 1U << (bucket_id % 8);
for (const LiteralIndex &lit_id : ids) {
const hwlmLiteral &l = lits[lit_id];
DEBUG_PRINTF("putting lit %u into bucket %u\n", lit_id, bucket_id);
const u32 sz = verify_u32(l.s.size());
for (u32 j = 0; j < numMasks; j++) {
const u32 msk_id_lo = j * 2 * maskWidth + (bucket_id / 8);
const u32 msk_id_hi = (j * 2 + 1) * maskWidth + (bucket_id / 8);
const u32 lo_base0 = msk_id_lo * 32;
const u32 lo_base1 = msk_id_lo * 32 + 16;
const u32 hi_base0 = msk_id_hi * 32;
const u32 hi_base1 = msk_id_hi * 32 + 16;
if (j >= sz) {
for (u32 n = 0; n < 16; n++) {
baseMsk[lo_base0 + n] &= ~bmsk;
baseMsk[lo_base1 + n] &= ~bmsk;
baseMsk[hi_base0 + n] &= ~bmsk;
baseMsk[hi_base1 + n] &= ~bmsk;
}
} else {
u8 c = l.s[sz - 1 - j];
const u32 hiShift = 4;
u32 n_hi = (c >> hiShift) & 0xf;
u32 n_lo = c & 0xf;
if (j < l.msk.size() && l.msk[l.msk.size() - 1 - j]) {
u8 m = l.msk[l.msk.size() - 1 - j];
u8 m_hi = (m >> hiShift) & 0xf;
u8 m_lo = m & 0xf;
u8 cmp = l.cmp[l.msk.size() - 1 - j];
u8 cmp_lo = cmp & 0xf;
u8 cmp_hi = (cmp >> hiShift) & 0xf;
for (u8 cm = 0; cm < 0x10; cm++) {
if ((cm & m_lo) == (cmp_lo & m_lo)) {
baseMsk[lo_base0 + cm] &= ~bmsk;
baseMsk[lo_base1 + cm] &= ~bmsk;
}
if ((cm & m_hi) == (cmp_hi & m_hi)) {
baseMsk[hi_base0 + cm] &= ~bmsk;
baseMsk[hi_base1 + cm] &= ~bmsk;
}
}
} else {
if (l.nocase && ourisalpha(c)) {
u32 cmHalfClear = (0xdf >> hiShift) & 0xf;
u32 cmHalfSet = (0x20 >> hiShift) & 0xf;
baseMsk[hi_base0 + (n_hi & cmHalfClear)] &= ~bmsk;
baseMsk[hi_base1 + (n_hi & cmHalfClear)] &= ~bmsk;
baseMsk[hi_base0 + (n_hi | cmHalfSet)] &= ~bmsk;
baseMsk[hi_base1 + (n_hi | cmHalfSet)] &= ~bmsk;
} else {
baseMsk[hi_base0 + n_hi] &= ~bmsk;
baseMsk[hi_base1 + n_hi] &= ~bmsk;
}
baseMsk[lo_base0 + n_lo] &= ~bmsk;
baseMsk[lo_base1 + n_lo] &= ~bmsk;
}
}
}
}
}
}
static
void fillNibbleMasks(const map<BucketIndex,
vector<LiteralIndex>> &bucketToLits,
const vector<hwlmLiteral> &lits,
u32 numMasks, u32 maskWidth, size_t maskLen,
u8 *baseMsk) {
memset(baseMsk, 0xff, maskLen);
for (const auto &b2l : bucketToLits) {
const u32 &bucket_id = b2l.first;
const vector<LiteralIndex> &ids = b2l.second;
const u8 bmsk = 1U << (bucket_id % 8);
for (const LiteralIndex &lit_id : ids) {
const hwlmLiteral &l = lits[lit_id];
DEBUG_PRINTF("putting lit %u into bucket %u\n", lit_id, bucket_id);
const u32 sz = verify_u32(l.s.size());
for (u32 j = 0; j < numMasks; j++) {
const u32 msk_id_lo = j * 2 * maskWidth + (bucket_id / 8);
const u32 msk_id_hi = (j * 2 + 1) * maskWidth + (bucket_id / 8);
const u32 lo_base = msk_id_lo * 16;
const u32 hi_base = msk_id_hi * 16;
if (j >= sz) {
for (u32 n = 0; n < 16; n++) {
baseMsk[lo_base + n] &= ~bmsk;
baseMsk[hi_base + n] &= ~bmsk;
}
} else {
u8 c = l.s[sz - 1 - j];
const u32 hiShift = 4;
u32 n_hi = (c >> hiShift) & 0xf;
u32 n_lo = c & 0xf;
if (j < l.msk.size() && l.msk[l.msk.size() - 1 - j]) {
u8 m = l.msk[l.msk.size() - 1 - j];
u8 m_hi = (m >> hiShift) & 0xf;
u8 m_lo = m & 0xf;
u8 cmp = l.cmp[l.msk.size() - 1 - j];
u8 cmp_lo = cmp & 0xf;
u8 cmp_hi = (cmp >> hiShift) & 0xf;
for (u8 cm = 0; cm < 0x10; cm++) {
if ((cm & m_lo) == (cmp_lo & m_lo)) {
baseMsk[lo_base + cm] &= ~bmsk;
}
if ((cm & m_hi) == (cmp_hi & m_hi)) {
baseMsk[hi_base + cm] &= ~bmsk;
}
}
} else {
if (l.nocase && ourisalpha(c)) {
u32 cmHalfClear = (0xdf >> hiShift) & 0xf;
u32 cmHalfSet = (0x20 >> hiShift) & 0xf;
baseMsk[hi_base + (n_hi & cmHalfClear)] &= ~bmsk;
baseMsk[hi_base + (n_hi | cmHalfSet)] &= ~bmsk;
} else {
baseMsk[hi_base + n_hi] &= ~bmsk;
}
baseMsk[lo_base + n_lo] &= ~bmsk;
}
}
}
}
}
}
static
void fillReinforcedTable(const map<BucketIndex,
vector<LiteralIndex>> &bucketToLits,
const vector<hwlmLiteral> &lits,
u8 *rtable_base, const u32 num_tables) {
vector<u8 *> tables;
for (u32 i = 0; i < num_tables; i++) {
tables.push_back(rtable_base + i * RTABLE_SIZE);
}
for (auto t : tables) {
initReinforcedTable(t);
}
for (const auto &b2l : bucketToLits) {
const u32 &bucket_id = b2l.first;
const vector<LiteralIndex> &ids = b2l.second;
u8 *rmsk = tables[bucket_id / 8];
const u8 bmsk = 1U << (bucket_id % 8);
for (const LiteralIndex &lit_id : ids) {
const hwlmLiteral &l = lits[lit_id];
DEBUG_PRINTF("putting lit %u into bucket %u\n", lit_id, bucket_id);
const u32 sz = verify_u32(l.s.size());
for (u32 j = 1; j < REINFORCED_MSK_LEN; j++) {
if (sz - 1 < j) {
fillReinforcedMsk(rmsk, ALL_CHAR_SET, j, bmsk);
} else {
u8 c = l.s[sz - 1 - j];
if (l.nocase && ourisalpha(c)) {
u8 c_up = c & 0xdf;
fillReinforcedMsk(rmsk, c_up, j, bmsk);
u8 c_lo = c | 0x20;
fillReinforcedMsk(rmsk, c_lo, j, bmsk);
} else {
fillReinforcedMsk(rmsk, c, j, bmsk);
}
}
}
}
}
for (auto t : tables) {
fillReinforcedMskZero(t);
}
}
bytecode_ptr<FDR> TeddyCompiler::build() {
u32 maskWidth = eng.getNumBuckets() / 8;
size_t headerSize = sizeof(Teddy);
size_t maskLen = eng.numMasks * 16 * 2 * maskWidth;
size_t reinforcedDupMaskLen = RTABLE_SIZE * maskWidth;
if (maskWidth == 2) { reinforcedDupMaskLen = maskLen * 2;
}
auto floodTable = setupFDRFloodControl(lits, eng, grey);
auto confirmTable = setupFullConfs(lits, eng, bucketToLits, make_small);
size_t size = ROUNDUP_CL(headerSize) + ROUNDUP_CL(maskLen) +
ROUNDUP_CL(reinforcedDupMaskLen) +
ROUNDUP_CL(confirmTable.size()) + floodTable.size();
auto fdr = make_zeroed_bytecode_ptr<FDR>(size, 64);
assert(fdr); Teddy *teddy = (Teddy *)fdr.get(); u8 *teddy_base = (u8 *)teddy;
teddy->size = size;
teddy->engineID = eng.getID();
teddy->maxStringLen = verify_u32(maxLen(lits));
teddy->numStrings = verify_u32(lits.size());
u8 *ptr = teddy_base + ROUNDUP_CL(headerSize) + ROUNDUP_CL(maskLen) +
ROUNDUP_CL(reinforcedDupMaskLen);
assert(ISALIGNED_CL(ptr));
teddy->confOffset = verify_u32(ptr - teddy_base);
memcpy(ptr, confirmTable.get(), confirmTable.size());
ptr += ROUNDUP_CL(confirmTable.size());
assert(ISALIGNED_CL(ptr));
teddy->floodOffset = verify_u32(ptr - teddy_base);
memcpy(ptr, floodTable.get(), floodTable.size());
ptr += floodTable.size();
u8 *baseMsk = teddy_base + ROUNDUP_CL(headerSize);
fillNibbleMasks(bucketToLits, lits, eng.numMasks, maskWidth, maskLen,
baseMsk);
if (maskWidth == 1) { u8 *reinforcedMsk = baseMsk + ROUNDUP_CL(maskLen);
fillReinforcedTable(bucketToLits, lits, reinforcedMsk, maskWidth);
} else { assert(maskWidth == 2);
u8 *dupMsk = baseMsk + ROUNDUP_CL(maskLen);
fillDupNibbleMasks(bucketToLits, lits, eng.numMasks,
reinforcedDupMaskLen, dupMsk);
}
return fdr;
}
static
bool assignStringsToBuckets(
const vector<hwlmLiteral> &lits,
TeddyEngineDescription &eng,
map<BucketIndex, vector<LiteralIndex>> &bucketToLits) {
assert(eng.numMasks <= MAX_NUM_MASKS);
if (lits.size() > eng.getNumBuckets() * TEDDY_BUCKET_LOAD) {
DEBUG_PRINTF("too many literals: %zu\n", lits.size());
return false;
}
#ifdef TEDDY_DEBUG
for (size_t i = 0; i < lits.size(); i++) {
printf("lit %zu (len = %zu, %s) is ", i, lits[i].s.size(),
lits[i].nocase ? "caseless" : "caseful");
for (size_t j = 0; j < lits[i].s.size(); j++) {
printf("%02x", ((u32)lits[i].s[j])&0xff);
}
printf("\n");
}
#endif
if (!pack(lits, eng, bucketToLits)) {
DEBUG_PRINTF("more lits (%zu) than buckets (%u), can't pack.\n",
lits.size(), eng.getNumBuckets());
return false;
}
return true;
}
}
bytecode_ptr<FDR> teddyBuildTable(const HWLMProto &proto, const Grey &grey) {
TeddyCompiler tc(proto.lits, proto.bucketToLits, *(proto.teddyEng),
proto.make_small, grey);
return tc.build();
}
unique_ptr<HWLMProto> teddyBuildProtoHinted(
u8 engType, const vector<hwlmLiteral> &lits,
bool make_small, u32 hint, const target_t &target) {
unique_ptr<TeddyEngineDescription> des;
if (hint == HINT_INVALID) {
des = chooseTeddyEngine(target, lits);
} else {
des = getTeddyDescription(hint);
}
if (!des) {
return nullptr;
}
map<BucketIndex, std::vector<LiteralIndex>> bucketToLits;
if (!assignStringsToBuckets(lits, *des, bucketToLits)) {
return nullptr;
}
return ue2::make_unique<HWLMProto>(engType, move(des), lits,
bucketToLits, make_small);
}
}