#include "stdafx.h"
#include <algorithm>
#include <vector>
#include "compress.h"
#include "compr_util.h"
#include "compr_entropy.h"
#include "qsort.h"
#include "match_hasher.h"
#include "compr_match_finder.h"
#include "compr_leviathan.h"
#include "compr_kraken.h"
#include "compr_mermaid.h"
int ilog2round(uint v) {
union { float f; uint32 u; };
f = (float)v;
return ((u + 0x257D86) >> 23) - 127;
}
void *LzScratchBlock::Allocate(int wanted_size) {
if (!ptr) {
size = wanted_size;
ptr = new uint8[wanted_size];
} else {
assert(wanted_size <= size);
}
return ptr;
}
LzScratchBlock::~LzScratchBlock() {
delete[](uint8*)ptr;
}
void SetupCompressionOptions(CompressOptions *copts) {
memset(copts, 0, sizeof(CompressOptions));
copts->maxLocalDictionarySize = 0x400000;
}
int GetHashBits(int src_len, int level, const CompressOptions *copts, int A, int B, int C, int D) {
int len = clamp(src_len, INT_MIN, INT_MAX);
if (copts->seekChunkReset && len > copts->seekChunkLen)
len = copts->seekChunkLen;
int bits = ilog2round(len);
if (level > 2) {
bits = clamp(bits, C, D);
} else {
bits = clamp(bits - 1, A, B);
}
if (copts->hashBits > 0) {
if (copts->hashBits <= 100) {
bits = clamp(std::min(bits, copts->hashBits), 12, 26);
} else {
bits = clamp(copts->hashBits - 100, 8, 28);
}
}
return bits;
}
int GetCompressedBufferSizeNeeded(int size) {
return size + 274 * ((size + 0x3FFFF) / 0x40000);
}
#define IS_BYTE_INSIDE(a, b, c) ((uint8)((a) - (b)) <= ((c) - (b)))
bool IsBlockProbablyText(const uint8 *p, const uint8 *p_end) {
const uint8 *ps = p;
while (p < p_end && IS_BYTE_INSIDE(*p, 0x80, 0xBF))
p++;
if (p - ps > 3)
return false;
while (p < p_end) {
uint8 c = *p++;
if (!IS_BYTE_INSIDE(c, 9, 0x7e)) {
if (!IS_BYTE_INSIDE(c, 0xc1, 0xf4))
return false;
size_t left = p_end - p;
if (c < 0xe0) {
check_1: if (left == 0)
break;
p += 1;
if (!IS_BYTE_INSIDE(p[-1], 0x80, 0xBF))
return false;
} else if (c < 0xf0) {
check_2: if (left < 2)
goto check_1;
p += 2;
if (!(IS_BYTE_INSIDE(p[-2], 0x80, 0xBF) && IS_BYTE_INSIDE(p[-1], 0x80, 0xBF)))
return false;
} else {
if (left < 3)
goto check_2;
p += 3;
if (!(IS_BYTE_INSIDE(p[-3], 0x80, 0xBF) && IS_BYTE_INSIDE(p[-2], 0x80, 0xBF) && IS_BYTE_INSIDE(p[-1], 0x80, 0xBF)))
return false;
}
}
}
return true;
}
bool IsProbablyText(const uint8 *p, size_t size) {
const uint8 *p_org = p;
if (size / 32 < 32)
return false;
int score = 0;
for (size_t i = 0; i != 32; i++, p += size / 32)
score += IsBlockProbablyText(p, p_org + std::min<size_t>(p + 32 - p_org, size));
return score >= 14;
}
uint8 *WriteBlockHdr(uint8 *dst, int compr_id, bool crc, bool keyframe, bool uncompressed) {
dst[0] = 12 + uncompressed * 0x40 + keyframe * 0x80;
dst[1] = compr_id + crc * 0x80;
return dst + 2;
}
bool AreAllBytesEqual(const uint8 *data, size_t size) {
if (size <= 1)
return true;
uint8 v = data[0];
for (size_t i = 1; i != size; i++)
if (data[i] != v)
return false;
return true;
}
uint8 *WriteMemsetQuantumHeader(uint8 *dst, uint8 v) {
*dst++ = 7;
*dst++ = 0xff;
*dst++ = 0xff;
*dst++ = v;
return dst;
}
uint8 *WriteBE24(uint8 *dst, uint32 v) {
*dst++ = (uint8)(v >> 16);
*dst++ = (uint8)(v >> 8);
*dst++ = (uint8)(v >> 0);
return dst;
}
void SubtractBytesUnsafe(uint8 *dst, const uint8 *src, size_t len, size_t neg_offs) {
if (len > 8) {
size_t loops = (len + 7) / 16;
do {
_mm_storeu_si128((__m128i *)dst, _mm_sub_epi8(_mm_loadu_si128((const __m128i *)src),
_mm_loadu_si128((const __m128i *)&src[neg_offs])));
src += 16, dst += 16;
} while (--loops);
}
_mm_storel_epi64((__m128i *)dst, _mm_sub_epi8(_mm_loadl_epi64((const __m128i *)src),
_mm_loadl_epi64((const __m128i *)&src[neg_offs])));
}
void SubtractBytes(uint8 *dst, const uint8 *src, size_t len, size_t neg_offs) {
for (; len >= 16; len -= 16, src += 16, dst += 16)
_mm_storeu_si128((__m128i *)dst, _mm_sub_epi8(_mm_loadu_si128((const __m128i *)src), _mm_loadu_si128((const __m128i *)&src[neg_offs])));
for (; len; len--, src++, dst++)
dst[0] = src[0] - src[neg_offs];
}
int CompressQuantum(LzCoder *coder, LzTemp *lztemp, MatchLenStorage *mls,
uint8 *src, int src_size,
uint8 *dst, uint8 *dst_end, int offset, float *cost_ptr) {
uint8 *dst_org = dst, *src_org = src;
uint8 *src_end = src + src_size;
float total_cost = 0;
while (src < src_end) {
int round_bytes = std::min<int>(src_end - src, coder->quantum_blocksize);
float memset_cost = GetTime_Memset(coder->platforms, round_bytes) * coder->speed_tradeoff + round_bytes + 3;
if (round_bytes >= 32) {
if (AreAllBytesEqual(src, round_bytes)) {
float memset_cost = kInvalidCost;
int n = EncodeArrayU8_Memset(dst, dst_end, src, round_bytes, coder->entropy_opts, coder->speed_tradeoff, coder->platforms, &memset_cost);
src += round_bytes;
dst += n;
total_cost += memset_cost;
} else {
float lzcost = kInvalidCost;
int chunk_type = -1, n;
if (coder->codec_id == kCompressorLeviathan) {
n = LeviathanDoCompress(coder, lztemp, mls, src, round_bytes, dst + 3, dst_end, offset + src - src_org, &chunk_type, &lzcost);
} else if (coder->codec_id == kCompressorKraken) {
n = KrakenDoCompress(coder, lztemp, mls, src, round_bytes, dst + 3, dst_end, offset + src - src_org, &chunk_type, &lzcost);
} else if (coder->codec_id == kCompressorMermaid || coder->codec_id == kCompressorSelkie) {
n = MermaidDoCompress(coder, lztemp, mls, src, round_bytes, dst + 3, dst_end, offset + src - src_org, &chunk_type, &lzcost);
} else {
return -1;
}
lzcost += 3;
int plain_huff_n = 0;
float plain_huff_cost = kInvalidCost;
uint8 *plain_huff_dst = NULL;
if (coder->check_plain_huffman) {
plain_huff_cost = std::min(memset_cost, lzcost);
plain_huff_dst = (uint8*)lztemp->scratch2.Allocate((round_bytes >> 4) + round_bytes + 256);
int plain_huff_dst_size = lztemp->scratch2.size;
plain_huff_n = EncodeArrayU8(plain_huff_dst, plain_huff_dst + plain_huff_dst_size,
src, round_bytes,
coder->entropy_opts, coder->speed_tradeoff, coder->platforms,
&plain_huff_cost,
coder->compression_level, NULL);
if (plain_huff_n < 0 || plain_huff_n >= round_bytes)
plain_huff_cost = kInvalidCost;
}
if (lzcost < memset_cost && lzcost <= plain_huff_cost && n >= 0 && n < round_bytes) {
dst = WriteBE24(dst, n | chunk_type << 19 | 0x800000) + n;
total_cost += lzcost;
} else if (memset_cost <= plain_huff_cost) {
WriteBE24(dst, round_bytes | 0x800000);
memcpy(dst + 3, src, round_bytes);
dst += 3 + round_bytes;
total_cost += memset_cost;
} else {
memcpy(dst, plain_huff_dst, plain_huff_n);
dst += plain_huff_n;
total_cost += plain_huff_cost;
}
}
} else {
WriteBE24(dst, round_bytes | 0x800000);
memcpy(dst + 3, src, round_bytes);
dst += 3 + round_bytes;
total_cost += memset_cost;
}
src += round_bytes;
}
*cost_ptr = total_cost;
return dst - dst_org;
}
int CompressBlocks(LzCoder *coder, LzTemp *lzcomp,
uint8 *src, uint8 *dst, int src_size,
uint8 *dict_base, uint8 *window_base,
LRMTable *lrm, MatchLenStorage *mls) {
uint8 *dst_org = dst;
uint8 *src_org = src;
uint8 *src_end = src + src_size;
while (src < src_end) {
int round_bytes = std::min<int>(src_end - src, 0x40000);
int bufsize_needed = GetCompressedBufferSizeNeeded(round_bytes);
bool keyframe = (src == dict_base);
uint8 *dst_blk = WriteBlockHdr(dst, coder->compressor_file_id, coder->opts->makeQHCrc, keyframe, false);
if (AreAllBytesEqual(src, round_bytes)) {
dst = WriteMemsetQuantumHeader(dst_blk, src[0]);
} else {
uint8 *dst_qh = WriteBE24(dst_blk, round_bytes - 1);
float cost = kInvalidCost;
int qn = CompressQuantum(coder, lzcomp, mls, src, round_bytes, dst_qh, dst + bufsize_needed, src - window_base, &cost);
float memset_cost = GetTime_Memset(coder->platforms, round_bytes) * coder->speed_tradeoff + round_bytes + 3;
if (qn < 0 || qn >= round_bytes || cost > memset_cost) {
dst = WriteBlockHdr(dst, coder->compressor_file_id, 0, keyframe, true);
memcpy(dst, src, round_bytes);
dst += round_bytes;
} else {
WriteBE24(dst_blk, qn - 1);
dst = dst_qh + qn;
}
}
src += round_bytes;
}
return dst - dst_org;
}
static const int lrm_step_size = 6;
static const int lrm_hash_length = 8;
static const int lrm_hash_lookup_bits_base = 10;
int Compress(LzCoder *coder, uint8 *src_in, uint8 *dst, int src_size, uint8 *src_window_base, LRMCascade *lrm_org) {
LRMCascade *lrm = lrm_org;
uint8 *dst_org = dst;
LzTemp lztemp;
if (!src_window_base || coder->opts->seekChunkReset)
src_window_base = src_in;
if (coder->compression_level >= 5) {
int total_window = src_in + src_size - src_window_base;
int src_size_left = src_size;
int local_dictsize = coder->opts->maxLocalDictionarySize;
if (!coder->limit_local_dictsize && !lrm)
local_dictsize = std::max(local_dictsize, 0x4000000);
int bytes_per_round;
if (total_window > local_dictsize) {
bytes_per_round = local_dictsize >> 1;
if (coder->opts->makeLongRangeMatcher && lrm_org == NULL && coder->compression_level >= 5) {
int lrmsize = bytes_per_round * ((total_window + bytes_per_round - 1) / bytes_per_round) - local_dictsize;
lrm = LRM_AllocateCascade(src_window_base, lrmsize, lrm_step_size, lrm_hash_lookup_bits_base, 0, bytes_per_round, lrm_hash_length);
}
} else {
bytes_per_round = total_window;
}
uint8 *src_cur = src_in;
while (src_size_left > 0) {
int round_bytes = std::min(src_size_left, bytes_per_round);
uint8 *cur_window_base = src_window_base;
if (src_cur == src_window_base)
round_bytes = std::min(src_size_left, local_dictsize);
if (src_size_left <= 5 * bytes_per_round / 4)
round_bytes = src_size_left;
int dictsize = 0;
if (round_bytes < local_dictsize)
dictsize = std::min<int>(std::min<int>(bytes_per_round, src_cur - src_window_base), local_dictsize - round_bytes);
if (coder->opts->dictionarySize > 0)
dictsize = std::min(dictsize, coder->opts->dictionarySize);
uint8 *dict_base = src_cur - dictsize;
LRMTable lrm_table_buf, *lrm_table = NULL;
if (lrm && dict_base > src_window_base) {
lrm_table = &lrm_table_buf;
LRM_GetRanges(lrm, &lrm_table_buf, dict_base, src_cur);
}
MatchLenStorage *mls = MatchLenStorage::Create(round_bytes + 1, 8.0f);
mls->window_base = src_cur;
if (coder->compression_level >= 6) {
FindMatchesSuffixTrie(dict_base, src_cur - dict_base + round_bytes, mls, 4, src_cur - dict_base, lrm_table);
} else {
FindMatchesHashBased(dict_base, src_cur - dict_base + round_bytes, mls, 4, src_cur - dict_base, lrm_table);
}
int n = CompressBlocks(coder, &lztemp, src_cur, dst, round_bytes, dict_base, cur_window_base, lrm_table, mls);
if (mls)
MatchLenStorage::Destroy(mls);
dst += n;
src_cur += round_bytes;
src_size_left -= round_bytes;
}
if (lrm != lrm_org)
LRM_FreeCascade(lrm);
} else {
int n = CompressBlocks(coder, &lztemp, src_in, dst, src_size, src_window_base, src_window_base, NULL, NULL);
dst += n;
}
return dst - dst_org;
}
const CompressOptions *GetDefaultCompressOpts(int level) {
static const CompressOptions compress_options_level5 = { 0, 0, 0, 0x40000, 0, 0, 0x100, 4, 0, 0x400000, 1, 0 };
static const CompressOptions compress_options_level4 = { 0, 0, 0, 0x40000, 0, 0, 0x100, 2, 0, 0x400000, 1, 0 };
static const CompressOptions compress_options_level0 = { 0, 0, 0, 0x40000, 0, 0, 0x100, 1, 0, 0x400000, 0, 0 };
return (level >= 5) ? &compress_options_level5 : (level >= 4) ? &compress_options_level4 : &compress_options_level0;
}
int CompressBlock_Leviathan(uint8 *src_in, uint8 *dst_in, int src_size, int level,
const CompressOptions *compressopts, uint8 *src_window_base, LRMCascade *lrm) {
LzCoder coder = { 0 };
if (!compressopts)
compressopts = GetDefaultCompressOpts(level);
if (src_window_base == NULL)
src_window_base = src_in;
coder.last_chunk_type = -1;
SetupEncoder_Leviathan(&coder, src_size, level, compressopts, src_window_base, src_in);
int n = Compress(&coder, src_in, dst_in, src_size, src_window_base, lrm);
return n;
}
int CompressBlock_Kraken(uint8 *src_in, uint8 *dst_in, int src_size, int level,
const CompressOptions *compressopts, uint8 *src_window_base, LRMCascade *lrm) {
LzCoder coder = { 0 };
if (!compressopts)
compressopts = GetDefaultCompressOpts(level);
if (src_window_base == NULL)
src_window_base = src_in;
coder.last_chunk_type = -1;
SetupEncoder_Kraken(&coder, src_size, level, compressopts, src_window_base, src_in);
int n = Compress(&coder, src_in, dst_in, src_size, src_window_base, lrm);
return n;
}
int CompressBlock_Mermaid(int codec_id, uint8 *src_in, uint8 *dst_in, int src_size, int level,
const CompressOptions *compressopts, uint8 *src_window_base, LRMCascade *lrm) {
LzCoder coder = { 0 };
if (!compressopts)
compressopts = GetDefaultCompressOpts(level);
if (src_window_base == NULL)
src_window_base = src_in;
coder.last_chunk_type = -1;
SetupEncoder_Mermaid(&coder, codec_id, src_size, level, compressopts, src_window_base, src_in);
int n = Compress(&coder, src_in, dst_in, src_size, src_window_base, lrm);
return n;
}
int CompressBlock(int codec_id, uint8 *src_in, uint8 *dst_in, int src_size, int level,
const CompressOptions *compressopts, uint8 *src_window_base, LRMCascade *lrm) {
switch (codec_id) {
case kCompressorKraken: return CompressBlock_Kraken(src_in, dst_in, src_size, level, compressopts, src_window_base, lrm);
case kCompressorLeviathan: return CompressBlock_Leviathan(src_in, dst_in, src_size, level, compressopts, src_window_base, lrm);
case kCompressorMermaid:
case kCompressorSelkie: return CompressBlock_Mermaid(codec_id, src_in, dst_in, src_size, level, compressopts, src_window_base, lrm);
default:
return -1;
}
}