mirror of
https://github.com/google/brotli.git
synced 2024-11-08 13:20:05 +00:00
Update encoder
* pull `BROTLI_MAX_BACKWARD_LIMIT` to constants * split generic and Zopfli backward references code * pull hashers init and stitch invocation to encoder * make `dictionary_hash` a compilation unit * add `size hint` parameter * add new hasher * use `size hint` to pick new hasher for q4 * modernize clz guard (fix #495) * move `hash to binary tree` to separate file * add `Initialize` and `Cleanup` to all hashers * do not raise OOM if malloc(0) == NULL (fix #500)
This commit is contained in:
parent
8d3fdc1dfe
commit
345450ecef
@ -50,5 +50,6 @@
|
||||
/* Number of slack bytes for window size. Don't confuse
|
||||
with BROTLI_NUM_DISTANCE_SHORT_CODES. */
|
||||
#define BROTLI_WINDOW_GAP 16
|
||||
#define BROTLI_MAX_BACKWARD_LIMIT(W) (((size_t)1 << (W)) - BROTLI_WINDOW_GAP)
|
||||
|
||||
#endif /* BROTLI_COMMON_CONSTANTS_H_ */
|
||||
|
@ -8,205 +8,17 @@
|
||||
|
||||
#include "./backward_references.h"
|
||||
|
||||
#include <string.h> /* memcpy, memset */
|
||||
|
||||
#include "../common/constants.h"
|
||||
#include <brotli/types.h>
|
||||
#include "./command.h"
|
||||
#include "./fast_log.h"
|
||||
#include "./find_match_length.h"
|
||||
#include "./literal_cost.h"
|
||||
#include "./memory.h"
|
||||
#include "./port.h"
|
||||
#include "./prefix.h"
|
||||
#include "./quality.h"
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static const float kInfinity = 1.7e38f; /* ~= 2 ^ 127 */
|
||||
|
||||
void BrotliInitZopfliNodes(ZopfliNode* array, size_t length) {
|
||||
ZopfliNode stub;
|
||||
size_t i;
|
||||
stub.length = 1;
|
||||
stub.distance = 0;
|
||||
stub.insert_length = 0;
|
||||
stub.u.cost = kInfinity;
|
||||
for (i = 0; i < length; ++i) array[i] = stub;
|
||||
}
|
||||
|
||||
static BROTLI_INLINE uint32_t ZopfliNodeCopyLength(const ZopfliNode* self) {
|
||||
return self->length & 0xffffff;
|
||||
}
|
||||
|
||||
static BROTLI_INLINE uint32_t ZopfliNodeLengthCode(const ZopfliNode* self) {
|
||||
const uint32_t modifier = self->length >> 24;
|
||||
return ZopfliNodeCopyLength(self) + 9u - modifier;
|
||||
}
|
||||
|
||||
static BROTLI_INLINE uint32_t ZopfliNodeCopyDistance(const ZopfliNode* self) {
|
||||
return self->distance & 0x1ffffff;
|
||||
}
|
||||
|
||||
static BROTLI_INLINE uint32_t ZopfliNodeDistanceCode(const ZopfliNode* self) {
|
||||
const uint32_t short_code = self->distance >> 25;
|
||||
return short_code == 0 ?
|
||||
ZopfliNodeCopyDistance(self) + BROTLI_NUM_DISTANCE_SHORT_CODES - 1 :
|
||||
short_code - 1;
|
||||
}
|
||||
|
||||
static BROTLI_INLINE uint32_t ZopfliNodeCommandLength(const ZopfliNode* self) {
|
||||
return ZopfliNodeCopyLength(self) + self->insert_length;
|
||||
}
|
||||
|
||||
/* Histogram based cost model for zopflification. */
|
||||
typedef struct ZopfliCostModel {
|
||||
/* The insert and copy length symbols. */
|
||||
float cost_cmd_[BROTLI_NUM_COMMAND_SYMBOLS];
|
||||
float cost_dist_[BROTLI_NUM_DISTANCE_SYMBOLS];
|
||||
/* Cumulative costs of literals per position in the stream. */
|
||||
float* literal_costs_;
|
||||
float min_cost_cmd_;
|
||||
size_t num_bytes_;
|
||||
} ZopfliCostModel;
|
||||
|
||||
static void InitZopfliCostModel(
|
||||
MemoryManager* m, ZopfliCostModel* self, size_t num_bytes) {
|
||||
self->num_bytes_ = num_bytes;
|
||||
self->literal_costs_ = BROTLI_ALLOC(m, float, num_bytes + 2);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
}
|
||||
|
||||
static void CleanupZopfliCostModel(MemoryManager* m, ZopfliCostModel* self) {
|
||||
BROTLI_FREE(m, self->literal_costs_);
|
||||
}
|
||||
|
||||
static void SetCost(const uint32_t* histogram, size_t histogram_size,
|
||||
float* cost) {
|
||||
size_t sum = 0;
|
||||
float log2sum;
|
||||
size_t i;
|
||||
for (i = 0; i < histogram_size; i++) {
|
||||
sum += histogram[i];
|
||||
}
|
||||
log2sum = (float)FastLog2(sum);
|
||||
for (i = 0; i < histogram_size; i++) {
|
||||
if (histogram[i] == 0) {
|
||||
cost[i] = log2sum + 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Shannon bits for this symbol. */
|
||||
cost[i] = log2sum - (float)FastLog2(histogram[i]);
|
||||
|
||||
/* Cannot be coded with less than 1 bit */
|
||||
if (cost[i] < 1) cost[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void ZopfliCostModelSetFromCommands(ZopfliCostModel* self,
|
||||
size_t position,
|
||||
const uint8_t* ringbuffer,
|
||||
size_t ringbuffer_mask,
|
||||
const Command* commands,
|
||||
size_t num_commands,
|
||||
size_t last_insert_len) {
|
||||
uint32_t histogram_literal[BROTLI_NUM_LITERAL_SYMBOLS];
|
||||
uint32_t histogram_cmd[BROTLI_NUM_COMMAND_SYMBOLS];
|
||||
uint32_t histogram_dist[BROTLI_NUM_DISTANCE_SYMBOLS];
|
||||
float cost_literal[BROTLI_NUM_LITERAL_SYMBOLS];
|
||||
size_t pos = position - last_insert_len;
|
||||
float min_cost_cmd = kInfinity;
|
||||
size_t i;
|
||||
float* cost_cmd = self->cost_cmd_;
|
||||
|
||||
memset(histogram_literal, 0, sizeof(histogram_literal));
|
||||
memset(histogram_cmd, 0, sizeof(histogram_cmd));
|
||||
memset(histogram_dist, 0, sizeof(histogram_dist));
|
||||
|
||||
for (i = 0; i < num_commands; i++) {
|
||||
size_t inslength = commands[i].insert_len_;
|
||||
size_t copylength = CommandCopyLen(&commands[i]);
|
||||
size_t distcode = commands[i].dist_prefix_;
|
||||
size_t cmdcode = commands[i].cmd_prefix_;
|
||||
size_t j;
|
||||
|
||||
histogram_cmd[cmdcode]++;
|
||||
if (cmdcode >= 128) histogram_dist[distcode]++;
|
||||
|
||||
for (j = 0; j < inslength; j++) {
|
||||
histogram_literal[ringbuffer[(pos + j) & ringbuffer_mask]]++;
|
||||
}
|
||||
|
||||
pos += inslength + copylength;
|
||||
}
|
||||
|
||||
SetCost(histogram_literal, BROTLI_NUM_LITERAL_SYMBOLS, cost_literal);
|
||||
SetCost(histogram_cmd, BROTLI_NUM_COMMAND_SYMBOLS, cost_cmd);
|
||||
SetCost(histogram_dist, BROTLI_NUM_DISTANCE_SYMBOLS, self->cost_dist_);
|
||||
|
||||
for (i = 0; i < BROTLI_NUM_COMMAND_SYMBOLS; ++i) {
|
||||
min_cost_cmd = BROTLI_MIN(float, min_cost_cmd, cost_cmd[i]);
|
||||
}
|
||||
self->min_cost_cmd_ = min_cost_cmd;
|
||||
|
||||
{
|
||||
float* literal_costs = self->literal_costs_;
|
||||
size_t num_bytes = self->num_bytes_;
|
||||
literal_costs[0] = 0.0;
|
||||
for (i = 0; i < num_bytes; ++i) {
|
||||
literal_costs[i + 1] = literal_costs[i] +
|
||||
cost_literal[ringbuffer[(position + i) & ringbuffer_mask]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ZopfliCostModelSetFromLiteralCosts(ZopfliCostModel* self,
|
||||
size_t position,
|
||||
const uint8_t* ringbuffer,
|
||||
size_t ringbuffer_mask) {
|
||||
float* literal_costs = self->literal_costs_;
|
||||
float* cost_dist = self->cost_dist_;
|
||||
float* cost_cmd = self->cost_cmd_;
|
||||
size_t num_bytes = self->num_bytes_;
|
||||
size_t i;
|
||||
BrotliEstimateBitCostsForLiterals(position, num_bytes, ringbuffer_mask,
|
||||
ringbuffer, &literal_costs[1]);
|
||||
literal_costs[0] = 0.0;
|
||||
for (i = 0; i < num_bytes; ++i) {
|
||||
literal_costs[i + 1] += literal_costs[i];
|
||||
}
|
||||
for (i = 0; i < BROTLI_NUM_COMMAND_SYMBOLS; ++i) {
|
||||
cost_cmd[i] = (float)FastLog2(11 + (uint32_t)i);
|
||||
}
|
||||
for (i = 0; i < BROTLI_NUM_DISTANCE_SYMBOLS; ++i) {
|
||||
cost_dist[i] = (float)FastLog2(20 + (uint32_t)i);
|
||||
}
|
||||
self->min_cost_cmd_ = (float)FastLog2(11);
|
||||
}
|
||||
|
||||
static BROTLI_INLINE float ZopfliCostModelGetCommandCost(
|
||||
const ZopfliCostModel* self, uint16_t cmdcode) {
|
||||
return self->cost_cmd_[cmdcode];
|
||||
}
|
||||
|
||||
static BROTLI_INLINE float ZopfliCostModelGetDistanceCost(
|
||||
const ZopfliCostModel* self, size_t distcode) {
|
||||
return self->cost_dist_[distcode];
|
||||
}
|
||||
|
||||
static BROTLI_INLINE float ZopfliCostModelGetLiteralCosts(
|
||||
const ZopfliCostModel* self, size_t from, size_t to) {
|
||||
return self->literal_costs_[to] - self->literal_costs_[from];
|
||||
}
|
||||
|
||||
static BROTLI_INLINE float ZopfliCostModelGetMinCostCmd(
|
||||
const ZopfliCostModel* self) {
|
||||
return self->min_cost_cmd_;
|
||||
}
|
||||
|
||||
static BROTLI_INLINE size_t ComputeDistanceCode(size_t distance,
|
||||
size_t max_distance,
|
||||
const int* dist_cache) {
|
||||
@ -231,462 +43,6 @@ static BROTLI_INLINE size_t ComputeDistanceCode(size_t distance,
|
||||
return distance + BROTLI_NUM_DISTANCE_SHORT_CODES - 1;
|
||||
}
|
||||
|
||||
/* REQUIRES: len >= 2, start_pos <= pos */
|
||||
/* REQUIRES: cost < kInfinity, nodes[start_pos].cost < kInfinity */
|
||||
/* Maintains the "ZopfliNode array invariant". */
|
||||
static BROTLI_INLINE void UpdateZopfliNode(ZopfliNode* nodes, size_t pos,
|
||||
size_t start_pos, size_t len, size_t len_code, size_t dist,
|
||||
size_t short_code, float cost) {
|
||||
ZopfliNode* next = &nodes[pos + len];
|
||||
next->length = (uint32_t)(len | ((len + 9u - len_code) << 24));
|
||||
next->distance = (uint32_t)(dist | (short_code << 25));
|
||||
next->insert_length = (uint32_t)(pos - start_pos);
|
||||
next->u.cost = cost;
|
||||
}
|
||||
|
||||
typedef struct PosData {
|
||||
size_t pos;
|
||||
int distance_cache[4];
|
||||
float costdiff;
|
||||
float cost;
|
||||
} PosData;
|
||||
|
||||
/* Maintains the smallest 8 cost difference together with their positions */
|
||||
typedef struct StartPosQueue {
|
||||
PosData q_[8];
|
||||
size_t idx_;
|
||||
} StartPosQueue;
|
||||
|
||||
static BROTLI_INLINE void InitStartPosQueue(StartPosQueue* self) {
|
||||
self->idx_ = 0;
|
||||
}
|
||||
|
||||
static size_t StartPosQueueSize(const StartPosQueue* self) {
|
||||
return BROTLI_MIN(size_t, self->idx_, 8);
|
||||
}
|
||||
|
||||
static void StartPosQueuePush(StartPosQueue* self, const PosData* posdata) {
|
||||
size_t offset = ~(self->idx_++) & 7;
|
||||
size_t len = StartPosQueueSize(self);
|
||||
size_t i;
|
||||
PosData* q = self->q_;
|
||||
q[offset] = *posdata;
|
||||
/* Restore the sorted order. In the list of |len| items at most |len - 1|
|
||||
adjacent element comparisons / swaps are required. */
|
||||
for (i = 1; i < len; ++i) {
|
||||
if (q[offset & 7].costdiff > q[(offset + 1) & 7].costdiff) {
|
||||
BROTLI_SWAP(PosData, q, offset & 7, (offset + 1) & 7);
|
||||
}
|
||||
++offset;
|
||||
}
|
||||
}
|
||||
|
||||
static const PosData* StartPosQueueAt(const StartPosQueue* self, size_t k) {
|
||||
return &self->q_[(k - self->idx_) & 7];
|
||||
}
|
||||
|
||||
/* Returns the minimum possible copy length that can improve the cost of any */
|
||||
/* future position. */
|
||||
static size_t ComputeMinimumCopyLength(const float start_cost,
|
||||
const ZopfliNode* nodes,
|
||||
const size_t num_bytes,
|
||||
const size_t pos) {
|
||||
/* Compute the minimum possible cost of reaching any future position. */
|
||||
float min_cost = start_cost;
|
||||
size_t len = 2;
|
||||
size_t next_len_bucket = 4;
|
||||
size_t next_len_offset = 10;
|
||||
while (pos + len <= num_bytes && nodes[pos + len].u.cost <= min_cost) {
|
||||
/* We already reached (pos + len) with no more cost than the minimum
|
||||
possible cost of reaching anything from this pos, so there is no point in
|
||||
looking for lengths <= len. */
|
||||
++len;
|
||||
if (len == next_len_offset) {
|
||||
/* We reached the next copy length code bucket, so we add one more
|
||||
extra bit to the minimum cost. */
|
||||
min_cost += 1.0f;
|
||||
next_len_offset += next_len_bucket;
|
||||
next_len_bucket *= 2;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/* REQUIRES: nodes[pos].cost < kInfinity
|
||||
REQUIRES: nodes[0..pos] satisfies that "ZopfliNode array invariant". */
|
||||
static uint32_t ComputeDistanceShortcut(const size_t block_start,
|
||||
const size_t pos,
|
||||
const size_t max_backward,
|
||||
const ZopfliNode* nodes) {
|
||||
const size_t clen = ZopfliNodeCopyLength(&nodes[pos]);
|
||||
const size_t ilen = nodes[pos].insert_length;
|
||||
const size_t dist = ZopfliNodeCopyDistance(&nodes[pos]);
|
||||
/* Since |block_start + pos| is the end position of the command, the copy part
|
||||
starts from |block_start + pos - clen|. Distances that are greater than
|
||||
this or greater than |max_backward| are static dictionary references, and
|
||||
do not update the last distances. Also distance code 0 (last distance)
|
||||
does not update the last distances. */
|
||||
if (pos == 0) {
|
||||
return 0;
|
||||
} else if (dist + clen <= block_start + pos &&
|
||||
dist <= max_backward &&
|
||||
ZopfliNodeDistanceCode(&nodes[pos]) > 0) {
|
||||
return (uint32_t)pos;
|
||||
} else {
|
||||
return nodes[pos - clen - ilen].u.shortcut;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fills in dist_cache[0..3] with the last four distances (as defined by
|
||||
Section 4. of the Spec) that would be used at (block_start + pos) if we
|
||||
used the shortest path of commands from block_start, computed from
|
||||
nodes[0..pos]. The last four distances at block_start are in
|
||||
starting_dist_cache[0..3].
|
||||
REQUIRES: nodes[pos].cost < kInfinity
|
||||
REQUIRES: nodes[0..pos] satisfies that "ZopfliNode array invariant". */
|
||||
static void ComputeDistanceCache(const size_t pos,
|
||||
const int* starting_dist_cache,
|
||||
const ZopfliNode* nodes,
|
||||
int* dist_cache) {
|
||||
int idx = 0;
|
||||
size_t p = nodes[pos].u.shortcut;
|
||||
while (idx < 4 && p > 0) {
|
||||
const size_t ilen = nodes[p].insert_length;
|
||||
const size_t clen = ZopfliNodeCopyLength(&nodes[p]);
|
||||
const size_t dist = ZopfliNodeCopyDistance(&nodes[p]);
|
||||
dist_cache[idx++] = (int)dist;
|
||||
/* Because of prerequisite, p >= clen + ilen >= 2. */
|
||||
p = nodes[p - clen - ilen].u.shortcut;
|
||||
}
|
||||
for (; idx < 4; ++idx) {
|
||||
dist_cache[idx] = *starting_dist_cache++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Maintains "ZopfliNode array invariant" and pushes node to the queue, if it
|
||||
is eligible. */
|
||||
static void EvaluateNode(
|
||||
const size_t block_start, const size_t pos, const size_t max_backward_limit,
|
||||
const int* starting_dist_cache, const ZopfliCostModel* model,
|
||||
StartPosQueue* queue, ZopfliNode* nodes) {
|
||||
/* Save cost, because ComputeDistanceCache invalidates it. */
|
||||
float node_cost = nodes[pos].u.cost;
|
||||
nodes[pos].u.shortcut = ComputeDistanceShortcut(
|
||||
block_start, pos, max_backward_limit, nodes);
|
||||
if (node_cost <= ZopfliCostModelGetLiteralCosts(model, 0, pos)) {
|
||||
PosData posdata;
|
||||
posdata.pos = pos;
|
||||
posdata.cost = node_cost;
|
||||
posdata.costdiff = node_cost -
|
||||
ZopfliCostModelGetLiteralCosts(model, 0, pos);
|
||||
ComputeDistanceCache(
|
||||
pos, starting_dist_cache, nodes, posdata.distance_cache);
|
||||
StartPosQueuePush(queue, &posdata);
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns longest copy length. */
|
||||
static size_t UpdateNodes(
|
||||
const size_t num_bytes, const size_t block_start, const size_t pos,
|
||||
const uint8_t* ringbuffer, const size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params, const size_t max_backward_limit,
|
||||
const int* starting_dist_cache, const size_t num_matches,
|
||||
const BackwardMatch* matches, const ZopfliCostModel* model,
|
||||
StartPosQueue* queue, ZopfliNode* nodes) {
|
||||
const size_t cur_ix = block_start + pos;
|
||||
const size_t cur_ix_masked = cur_ix & ringbuffer_mask;
|
||||
const size_t max_distance = BROTLI_MIN(size_t, cur_ix, max_backward_limit);
|
||||
const size_t max_len = num_bytes - pos;
|
||||
const size_t max_zopfli_len = MaxZopfliLen(params);
|
||||
const size_t max_iters = MaxZopfliCandidates(params);
|
||||
size_t min_len;
|
||||
size_t result = 0;
|
||||
size_t k;
|
||||
|
||||
EvaluateNode(block_start, pos, max_backward_limit, starting_dist_cache, model,
|
||||
queue, nodes);
|
||||
|
||||
{
|
||||
const PosData* posdata = StartPosQueueAt(queue, 0);
|
||||
float min_cost = (posdata->cost + ZopfliCostModelGetMinCostCmd(model) +
|
||||
ZopfliCostModelGetLiteralCosts(model, posdata->pos, pos));
|
||||
min_len = ComputeMinimumCopyLength(min_cost, nodes, num_bytes, pos);
|
||||
}
|
||||
|
||||
/* Go over the command starting positions in order of increasing cost
|
||||
difference. */
|
||||
for (k = 0; k < max_iters && k < StartPosQueueSize(queue); ++k) {
|
||||
const PosData* posdata = StartPosQueueAt(queue, k);
|
||||
const size_t start = posdata->pos;
|
||||
const uint16_t inscode = GetInsertLengthCode(pos - start);
|
||||
const float start_costdiff = posdata->costdiff;
|
||||
const float base_cost = start_costdiff + (float)GetInsertExtra(inscode) +
|
||||
ZopfliCostModelGetLiteralCosts(model, 0, pos);
|
||||
|
||||
/* Look for last distance matches using the distance cache from this
|
||||
starting position. */
|
||||
size_t best_len = min_len - 1;
|
||||
size_t j = 0;
|
||||
for (; j < BROTLI_NUM_DISTANCE_SHORT_CODES && best_len < max_len; ++j) {
|
||||
const size_t idx = kDistanceCacheIndex[j];
|
||||
const size_t backward =
|
||||
(size_t)(posdata->distance_cache[idx] + kDistanceCacheOffset[j]);
|
||||
size_t prev_ix = cur_ix - backward;
|
||||
if (prev_ix >= cur_ix) {
|
||||
continue;
|
||||
}
|
||||
if (BROTLI_PREDICT_FALSE(backward > max_distance)) {
|
||||
continue;
|
||||
}
|
||||
prev_ix &= ringbuffer_mask;
|
||||
|
||||
if (cur_ix_masked + best_len > ringbuffer_mask ||
|
||||
prev_ix + best_len > ringbuffer_mask ||
|
||||
ringbuffer[cur_ix_masked + best_len] !=
|
||||
ringbuffer[prev_ix + best_len]) {
|
||||
continue;
|
||||
}
|
||||
{
|
||||
const size_t len =
|
||||
FindMatchLengthWithLimit(&ringbuffer[prev_ix],
|
||||
&ringbuffer[cur_ix_masked],
|
||||
max_len);
|
||||
const float dist_cost = base_cost +
|
||||
ZopfliCostModelGetDistanceCost(model, j);
|
||||
size_t l;
|
||||
for (l = best_len + 1; l <= len; ++l) {
|
||||
const uint16_t copycode = GetCopyLengthCode(l);
|
||||
const uint16_t cmdcode =
|
||||
CombineLengthCodes(inscode, copycode, j == 0);
|
||||
const float cost = (cmdcode < 128 ? base_cost : dist_cost) +
|
||||
(float)GetCopyExtra(copycode) +
|
||||
ZopfliCostModelGetCommandCost(model, cmdcode);
|
||||
if (cost < nodes[pos + l].u.cost) {
|
||||
UpdateZopfliNode(nodes, pos, start, l, l, backward, j + 1, cost);
|
||||
result = BROTLI_MAX(size_t, result, l);
|
||||
}
|
||||
best_len = l;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* At higher iterations look only for new last distance matches, since
|
||||
looking only for new command start positions with the same distances
|
||||
does not help much. */
|
||||
if (k >= 2) continue;
|
||||
|
||||
{
|
||||
/* Loop through all possible copy lengths at this position. */
|
||||
size_t len = min_len;
|
||||
for (j = 0; j < num_matches; ++j) {
|
||||
BackwardMatch match = matches[j];
|
||||
size_t dist = match.distance;
|
||||
BROTLI_BOOL is_dictionary_match = TO_BROTLI_BOOL(dist > max_distance);
|
||||
/* We already tried all possible last distance matches, so we can use
|
||||
normal distance code here. */
|
||||
size_t dist_code = dist + BROTLI_NUM_DISTANCE_SHORT_CODES - 1;
|
||||
uint16_t dist_symbol;
|
||||
uint32_t distextra;
|
||||
uint32_t distnumextra;
|
||||
float dist_cost;
|
||||
size_t max_match_len;
|
||||
PrefixEncodeCopyDistance(dist_code, 0, 0, &dist_symbol, &distextra);
|
||||
distnumextra = distextra >> 24;
|
||||
dist_cost = base_cost + (float)distnumextra +
|
||||
ZopfliCostModelGetDistanceCost(model, dist_symbol);
|
||||
|
||||
/* Try all copy lengths up until the maximum copy length corresponding
|
||||
to this distance. If the distance refers to the static dictionary, or
|
||||
the maximum length is long enough, try only one maximum length. */
|
||||
max_match_len = BackwardMatchLength(&match);
|
||||
if (len < max_match_len &&
|
||||
(is_dictionary_match || max_match_len > max_zopfli_len)) {
|
||||
len = max_match_len;
|
||||
}
|
||||
for (; len <= max_match_len; ++len) {
|
||||
const size_t len_code =
|
||||
is_dictionary_match ? BackwardMatchLengthCode(&match) : len;
|
||||
const uint16_t copycode = GetCopyLengthCode(len_code);
|
||||
const uint16_t cmdcode = CombineLengthCodes(inscode, copycode, 0);
|
||||
const float cost = dist_cost + (float)GetCopyExtra(copycode) +
|
||||
ZopfliCostModelGetCommandCost(model, cmdcode);
|
||||
if (cost < nodes[pos + len].u.cost) {
|
||||
UpdateZopfliNode(nodes, pos, start, len, len_code, dist, 0, cost);
|
||||
result = BROTLI_MAX(size_t, result, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static size_t ComputeShortestPathFromNodes(size_t num_bytes,
|
||||
ZopfliNode* nodes) {
|
||||
size_t index = num_bytes;
|
||||
size_t num_commands = 0;
|
||||
while (nodes[index].insert_length == 0 && nodes[index].length == 1) --index;
|
||||
nodes[index].u.next = BROTLI_UINT32_MAX;
|
||||
while (index != 0) {
|
||||
size_t len = ZopfliNodeCommandLength(&nodes[index]);
|
||||
index -= len;
|
||||
nodes[index].u.next = (uint32_t)len;
|
||||
num_commands++;
|
||||
}
|
||||
return num_commands;
|
||||
}
|
||||
|
||||
void BrotliZopfliCreateCommands(const size_t num_bytes,
|
||||
const size_t block_start,
|
||||
const size_t max_backward_limit,
|
||||
const ZopfliNode* nodes,
|
||||
int* dist_cache,
|
||||
size_t* last_insert_len,
|
||||
Command* commands,
|
||||
size_t* num_literals) {
|
||||
size_t pos = 0;
|
||||
uint32_t offset = nodes[0].u.next;
|
||||
size_t i;
|
||||
for (i = 0; offset != BROTLI_UINT32_MAX; i++) {
|
||||
const ZopfliNode* next = &nodes[pos + offset];
|
||||
size_t copy_length = ZopfliNodeCopyLength(next);
|
||||
size_t insert_length = next->insert_length;
|
||||
pos += insert_length;
|
||||
offset = next->u.next;
|
||||
if (i == 0) {
|
||||
insert_length += *last_insert_len;
|
||||
*last_insert_len = 0;
|
||||
}
|
||||
{
|
||||
size_t distance = ZopfliNodeCopyDistance(next);
|
||||
size_t len_code = ZopfliNodeLengthCode(next);
|
||||
size_t max_distance =
|
||||
BROTLI_MIN(size_t, block_start + pos, max_backward_limit);
|
||||
BROTLI_BOOL is_dictionary = TO_BROTLI_BOOL(distance > max_distance);
|
||||
size_t dist_code = ZopfliNodeDistanceCode(next);
|
||||
|
||||
InitCommand(
|
||||
&commands[i], insert_length, copy_length, len_code, dist_code);
|
||||
|
||||
if (!is_dictionary && dist_code > 0) {
|
||||
dist_cache[3] = dist_cache[2];
|
||||
dist_cache[2] = dist_cache[1];
|
||||
dist_cache[1] = dist_cache[0];
|
||||
dist_cache[0] = (int)distance;
|
||||
}
|
||||
}
|
||||
|
||||
*num_literals += insert_length;
|
||||
pos += copy_length;
|
||||
}
|
||||
*last_insert_len += num_bytes - pos;
|
||||
}
|
||||
|
||||
static size_t ZopfliIterate(size_t num_bytes,
|
||||
size_t position,
|
||||
const uint8_t* ringbuffer,
|
||||
size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params,
|
||||
const size_t max_backward_limit,
|
||||
const int* dist_cache,
|
||||
const ZopfliCostModel* model,
|
||||
const uint32_t* num_matches,
|
||||
const BackwardMatch* matches,
|
||||
ZopfliNode* nodes) {
|
||||
const size_t max_zopfli_len = MaxZopfliLen(params);
|
||||
StartPosQueue queue;
|
||||
size_t cur_match_pos = 0;
|
||||
size_t i;
|
||||
nodes[0].length = 0;
|
||||
nodes[0].u.cost = 0;
|
||||
InitStartPosQueue(&queue);
|
||||
for (i = 0; i + 3 < num_bytes; i++) {
|
||||
size_t skip = UpdateNodes(num_bytes, position, i, ringbuffer,
|
||||
ringbuffer_mask, params, max_backward_limit, dist_cache,
|
||||
num_matches[i], &matches[cur_match_pos], model, &queue, nodes);
|
||||
if (skip < BROTLI_LONG_COPY_QUICK_STEP) skip = 0;
|
||||
cur_match_pos += num_matches[i];
|
||||
if (num_matches[i] == 1 &&
|
||||
BackwardMatchLength(&matches[cur_match_pos - 1]) > max_zopfli_len) {
|
||||
skip = BROTLI_MAX(size_t,
|
||||
BackwardMatchLength(&matches[cur_match_pos - 1]), skip);
|
||||
}
|
||||
if (skip > 1) {
|
||||
skip--;
|
||||
while (skip) {
|
||||
i++;
|
||||
if (i + 3 >= num_bytes) break;
|
||||
EvaluateNode(
|
||||
position, i, max_backward_limit, dist_cache, model, &queue, nodes);
|
||||
cur_match_pos += num_matches[i];
|
||||
skip--;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ComputeShortestPathFromNodes(num_bytes, nodes);
|
||||
}
|
||||
|
||||
|
||||
size_t BrotliZopfliComputeShortestPath(MemoryManager* m,
|
||||
size_t num_bytes,
|
||||
size_t position,
|
||||
const uint8_t* ringbuffer,
|
||||
size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params,
|
||||
const size_t max_backward_limit,
|
||||
const int* dist_cache,
|
||||
H10* hasher,
|
||||
ZopfliNode* nodes) {
|
||||
const size_t max_zopfli_len = MaxZopfliLen(params);
|
||||
ZopfliCostModel model;
|
||||
StartPosQueue queue;
|
||||
BackwardMatch matches[MAX_NUM_MATCHES_H10];
|
||||
const size_t store_end = num_bytes >= StoreLookaheadH10() ?
|
||||
position + num_bytes - StoreLookaheadH10() + 1 : position;
|
||||
size_t i;
|
||||
nodes[0].length = 0;
|
||||
nodes[0].u.cost = 0;
|
||||
InitZopfliCostModel(m, &model, num_bytes);
|
||||
if (BROTLI_IS_OOM(m)) return 0;
|
||||
ZopfliCostModelSetFromLiteralCosts(
|
||||
&model, position, ringbuffer, ringbuffer_mask);
|
||||
InitStartPosQueue(&queue);
|
||||
for (i = 0; i + HashTypeLengthH10() - 1 < num_bytes; i++) {
|
||||
const size_t pos = position + i;
|
||||
const size_t max_distance = BROTLI_MIN(size_t, pos, max_backward_limit);
|
||||
size_t num_matches = FindAllMatchesH10(hasher, ringbuffer, ringbuffer_mask,
|
||||
pos, num_bytes - i, max_distance, params, matches);
|
||||
size_t skip;
|
||||
if (num_matches > 0 &&
|
||||
BackwardMatchLength(&matches[num_matches - 1]) > max_zopfli_len) {
|
||||
matches[0] = matches[num_matches - 1];
|
||||
num_matches = 1;
|
||||
}
|
||||
skip = UpdateNodes(num_bytes, position, i, ringbuffer, ringbuffer_mask,
|
||||
params, max_backward_limit, dist_cache, num_matches, matches, &model,
|
||||
&queue, nodes);
|
||||
if (skip < BROTLI_LONG_COPY_QUICK_STEP) skip = 0;
|
||||
if (num_matches == 1 && BackwardMatchLength(&matches[0]) > max_zopfli_len) {
|
||||
skip = BROTLI_MAX(size_t, BackwardMatchLength(&matches[0]), skip);
|
||||
}
|
||||
if (skip > 1) {
|
||||
/* Add the tail of the copy to the hasher. */
|
||||
StoreRangeH10(hasher, ringbuffer, ringbuffer_mask, pos + 1, BROTLI_MIN(
|
||||
size_t, pos + skip, store_end));
|
||||
skip--;
|
||||
while (skip) {
|
||||
i++;
|
||||
if (i + HashTypeLengthH10() - 1 >= num_bytes) break;
|
||||
EvaluateNode(
|
||||
position, i, max_backward_limit, dist_cache, &model, &queue, nodes);
|
||||
skip--;
|
||||
}
|
||||
}
|
||||
}
|
||||
CleanupZopfliCostModel(m, &model);
|
||||
return ComputeShortestPathFromNodes(num_bytes, nodes);
|
||||
}
|
||||
|
||||
#define EXPAND_CAT(a, b) CAT(a, b)
|
||||
#define CAT(a, b) a ## b
|
||||
#define FN(X) EXPAND_CAT(X, HASHER())
|
||||
@ -746,134 +102,17 @@ size_t BrotliZopfliComputeShortestPath(MemoryManager* m,
|
||||
#include "./backward_references_inc.h"
|
||||
#undef HASHER
|
||||
|
||||
#define HASHER() H54
|
||||
/* NOLINTNEXTLINE(build/include) */
|
||||
#include "./backward_references_inc.h"
|
||||
#undef HASHER
|
||||
|
||||
#undef FN
|
||||
#undef CAT
|
||||
#undef EXPAND_CAT
|
||||
|
||||
static BROTLI_NOINLINE void CreateZopfliBackwardReferences(
|
||||
MemoryManager* m, size_t num_bytes, size_t position, BROTLI_BOOL is_last,
|
||||
const uint8_t* ringbuffer, size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params, H10* hasher, int* dist_cache,
|
||||
size_t* last_insert_len, Command* commands, size_t* num_commands,
|
||||
size_t* num_literals) {
|
||||
const size_t max_backward_limit = MaxBackwardLimit(params->lgwin);
|
||||
ZopfliNode* nodes;
|
||||
InitH10(m, hasher, ringbuffer, params, position, num_bytes, is_last);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
StitchToPreviousBlockH10(hasher, num_bytes, position,
|
||||
ringbuffer, ringbuffer_mask);
|
||||
nodes = BROTLI_ALLOC(m, ZopfliNode, num_bytes + 1);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
BrotliInitZopfliNodes(nodes, num_bytes + 1);
|
||||
*num_commands += BrotliZopfliComputeShortestPath(m, num_bytes, position,
|
||||
ringbuffer, ringbuffer_mask, params, max_backward_limit,
|
||||
dist_cache, hasher, nodes);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
BrotliZopfliCreateCommands(num_bytes, position, max_backward_limit, nodes,
|
||||
dist_cache, last_insert_len, commands, num_literals);
|
||||
BROTLI_FREE(m, nodes);
|
||||
}
|
||||
|
||||
static BROTLI_NOINLINE void CreateHqZopfliBackwardReferences(
|
||||
MemoryManager* m, size_t num_bytes, size_t position, BROTLI_BOOL is_last,
|
||||
const uint8_t* ringbuffer, size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params, H10* hasher, int* dist_cache,
|
||||
size_t* last_insert_len, Command* commands, size_t* num_commands,
|
||||
size_t* num_literals) {
|
||||
const size_t max_backward_limit = MaxBackwardLimit(params->lgwin);
|
||||
uint32_t* num_matches = BROTLI_ALLOC(m, uint32_t, num_bytes);
|
||||
size_t matches_size = 4 * num_bytes;
|
||||
const size_t store_end = num_bytes >= StoreLookaheadH10() ?
|
||||
position + num_bytes - StoreLookaheadH10() + 1 : position;
|
||||
size_t cur_match_pos = 0;
|
||||
size_t i;
|
||||
size_t orig_num_literals;
|
||||
size_t orig_last_insert_len;
|
||||
int orig_dist_cache[4];
|
||||
size_t orig_num_commands;
|
||||
ZopfliCostModel model;
|
||||
ZopfliNode* nodes;
|
||||
BackwardMatch* matches = BROTLI_ALLOC(m, BackwardMatch, matches_size);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
InitH10(m, hasher, ringbuffer, params, position, num_bytes, is_last);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
StitchToPreviousBlockH10(hasher, num_bytes, position,
|
||||
ringbuffer, ringbuffer_mask);
|
||||
for (i = 0; i + HashTypeLengthH10() - 1 < num_bytes; ++i) {
|
||||
const size_t pos = position + i;
|
||||
size_t max_distance = BROTLI_MIN(size_t, pos, max_backward_limit);
|
||||
size_t max_length = num_bytes - i;
|
||||
size_t num_found_matches;
|
||||
size_t cur_match_end;
|
||||
size_t j;
|
||||
/* Ensure that we have enough free slots. */
|
||||
BROTLI_ENSURE_CAPACITY(m, BackwardMatch, matches, matches_size,
|
||||
cur_match_pos + MAX_NUM_MATCHES_H10);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
num_found_matches = FindAllMatchesH10(hasher, ringbuffer, ringbuffer_mask,
|
||||
pos, max_length, max_distance, params, &matches[cur_match_pos]);
|
||||
cur_match_end = cur_match_pos + num_found_matches;
|
||||
for (j = cur_match_pos; j + 1 < cur_match_end; ++j) {
|
||||
assert(BackwardMatchLength(&matches[j]) <
|
||||
BackwardMatchLength(&matches[j + 1]));
|
||||
assert(matches[j].distance > max_distance ||
|
||||
matches[j].distance <= matches[j + 1].distance);
|
||||
}
|
||||
num_matches[i] = (uint32_t)num_found_matches;
|
||||
if (num_found_matches > 0) {
|
||||
const size_t match_len = BackwardMatchLength(&matches[cur_match_end - 1]);
|
||||
if (match_len > MAX_ZOPFLI_LEN_QUALITY_11) {
|
||||
const size_t skip = match_len - 1;
|
||||
matches[cur_match_pos++] = matches[cur_match_end - 1];
|
||||
num_matches[i] = 1;
|
||||
/* Add the tail of the copy to the hasher. */
|
||||
StoreRangeH10(hasher, ringbuffer, ringbuffer_mask, pos + 1,
|
||||
BROTLI_MIN(size_t, pos + match_len, store_end));
|
||||
memset(&num_matches[i + 1], 0, skip * sizeof(num_matches[0]));
|
||||
i += skip;
|
||||
} else {
|
||||
cur_match_pos = cur_match_end;
|
||||
}
|
||||
}
|
||||
}
|
||||
orig_num_literals = *num_literals;
|
||||
orig_last_insert_len = *last_insert_len;
|
||||
memcpy(orig_dist_cache, dist_cache, 4 * sizeof(dist_cache[0]));
|
||||
orig_num_commands = *num_commands;
|
||||
nodes = BROTLI_ALLOC(m, ZopfliNode, num_bytes + 1);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
InitZopfliCostModel(m, &model, num_bytes);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
for (i = 0; i < 2; i++) {
|
||||
BrotliInitZopfliNodes(nodes, num_bytes + 1);
|
||||
if (i == 0) {
|
||||
ZopfliCostModelSetFromLiteralCosts(
|
||||
&model, position, ringbuffer, ringbuffer_mask);
|
||||
} else {
|
||||
ZopfliCostModelSetFromCommands(&model, position, ringbuffer,
|
||||
ringbuffer_mask, commands, *num_commands - orig_num_commands,
|
||||
orig_last_insert_len);
|
||||
}
|
||||
*num_commands = orig_num_commands;
|
||||
*num_literals = orig_num_literals;
|
||||
*last_insert_len = orig_last_insert_len;
|
||||
memcpy(dist_cache, orig_dist_cache, 4 * sizeof(dist_cache[0]));
|
||||
*num_commands += ZopfliIterate(num_bytes, position, ringbuffer,
|
||||
ringbuffer_mask, params, max_backward_limit, dist_cache,
|
||||
&model, num_matches, matches, nodes);
|
||||
BrotliZopfliCreateCommands(num_bytes, position, max_backward_limit,
|
||||
nodes, dist_cache, last_insert_len, commands, num_literals);
|
||||
}
|
||||
CleanupZopfliCostModel(m, &model);
|
||||
BROTLI_FREE(m, nodes);
|
||||
BROTLI_FREE(m, matches);
|
||||
BROTLI_FREE(m, num_matches);
|
||||
}
|
||||
|
||||
void BrotliCreateBackwardReferences(MemoryManager* m,
|
||||
size_t num_bytes,
|
||||
void BrotliCreateBackwardReferences(size_t num_bytes,
|
||||
size_t position,
|
||||
BROTLI_BOOL is_last,
|
||||
const uint8_t* ringbuffer,
|
||||
size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params,
|
||||
@ -883,24 +122,10 @@ void BrotliCreateBackwardReferences(MemoryManager* m,
|
||||
Command* commands,
|
||||
size_t* num_commands,
|
||||
size_t* num_literals) {
|
||||
if (params->quality == ZOPFLIFICATION_QUALITY) {
|
||||
CreateZopfliBackwardReferences(
|
||||
m, num_bytes, position, is_last, ringbuffer, ringbuffer_mask,
|
||||
params, hashers->h10, dist_cache,
|
||||
last_insert_len, commands, num_commands, num_literals);
|
||||
return;
|
||||
} else if (params->quality == HQ_ZOPFLIFICATION_QUALITY) {
|
||||
CreateHqZopfliBackwardReferences(
|
||||
m, num_bytes, position, is_last, ringbuffer, ringbuffer_mask,
|
||||
params, hashers->h10, dist_cache,
|
||||
last_insert_len, commands, num_commands, num_literals);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ChooseHasher(params)) {
|
||||
#define CASE_(N) \
|
||||
case N: \
|
||||
CreateBackwardReferencesH ## N(m, num_bytes, position, is_last, \
|
||||
CreateBackwardReferencesH ## N(num_bytes, position, \
|
||||
ringbuffer, ringbuffer_mask, params, hashers->h ## N, dist_cache, \
|
||||
last_insert_len, commands, num_commands, num_literals); \
|
||||
break;
|
||||
@ -909,7 +134,6 @@ void BrotliCreateBackwardReferences(MemoryManager* m,
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
}
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
|
@ -13,7 +13,6 @@
|
||||
#include <brotli/types.h>
|
||||
#include "./command.h"
|
||||
#include "./hash.h"
|
||||
#include "./memory.h"
|
||||
#include "./port.h"
|
||||
#include "./quality.h"
|
||||
|
||||
@ -26,73 +25,12 @@ extern "C" {
|
||||
CreateBackwardReferences calls, and must be incremented by the amount written
|
||||
by this call. */
|
||||
BROTLI_INTERNAL void BrotliCreateBackwardReferences(
|
||||
MemoryManager* m, size_t num_bytes, size_t position, BROTLI_BOOL is_last,
|
||||
size_t num_bytes, size_t position,
|
||||
const uint8_t* ringbuffer, size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params, Hashers* hashers, int* dist_cache,
|
||||
size_t* last_insert_len, Command* commands, size_t* num_commands,
|
||||
size_t* num_literals);
|
||||
|
||||
typedef struct ZopfliNode {
|
||||
/* best length to get up to this byte (not including this byte itself)
|
||||
highest 8 bit is used to reconstruct the length code */
|
||||
uint32_t length;
|
||||
/* distance associated with the length
|
||||
highest 7 bit contains distance short code + 1 (or zero if no short code)
|
||||
*/
|
||||
uint32_t distance;
|
||||
/* number of literal inserts before this copy */
|
||||
uint32_t insert_length;
|
||||
|
||||
/* This union holds information used by dynamic-programming. During forward
|
||||
pass |cost| it used to store the goal function. When node is processed its
|
||||
|cost| is invalidated in favor of |shortcut|. On path back-tracing pass
|
||||
|next| is assigned the offset to next node on the path. */
|
||||
union {
|
||||
/* Smallest cost to get to this byte from the beginning, as found so far. */
|
||||
float cost;
|
||||
/* Offset to the next node on the path. Equals to command_length() of the
|
||||
next node on the path. For last node equals to BROTLI_UINT32_MAX */
|
||||
uint32_t next;
|
||||
/* Node position that provides next distance for distance cache. */
|
||||
uint32_t shortcut;
|
||||
} u;
|
||||
} ZopfliNode;
|
||||
|
||||
BROTLI_INTERNAL void BrotliInitZopfliNodes(ZopfliNode* array, size_t length);
|
||||
|
||||
/* Computes the shortest path of commands from position to at most
|
||||
position + num_bytes.
|
||||
|
||||
On return, path->size() is the number of commands found and path[i] is the
|
||||
length of the i-th command (copy length plus insert length).
|
||||
Note that the sum of the lengths of all commands can be less than num_bytes.
|
||||
|
||||
On return, the nodes[0..num_bytes] array will have the following
|
||||
"ZopfliNode array invariant":
|
||||
For each i in [1..num_bytes], if nodes[i].cost < kInfinity, then
|
||||
(1) nodes[i].copy_length() >= 2
|
||||
(2) nodes[i].command_length() <= i and
|
||||
(3) nodes[i - nodes[i].command_length()].cost < kInfinity */
|
||||
BROTLI_INTERNAL size_t BrotliZopfliComputeShortestPath(
|
||||
MemoryManager* m, size_t num_bytes, size_t position,
|
||||
const uint8_t* ringbuffer, size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params, const size_t max_backward_limit,
|
||||
const int* dist_cache, H10* hasher, ZopfliNode* nodes);
|
||||
|
||||
BROTLI_INTERNAL void BrotliZopfliCreateCommands(const size_t num_bytes,
|
||||
const size_t block_start,
|
||||
const size_t max_backward_limit,
|
||||
const ZopfliNode* nodes,
|
||||
int* dist_cache,
|
||||
size_t* last_insert_len,
|
||||
Command* commands,
|
||||
size_t* num_literals);
|
||||
|
||||
/* Maximum distance, see section 9.1. of the spec. */
|
||||
static BROTLI_INLINE size_t MaxBackwardLimit(int lgwin) {
|
||||
return (1u << lgwin) - BROTLI_WINDOW_GAP;
|
||||
}
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
780
enc/backward_references_hq.c
Executable file
780
enc/backward_references_hq.c
Executable file
@ -0,0 +1,780 @@
|
||||
/* Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
Distributed under MIT license.
|
||||
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/* Function to find backward reference copies. */
|
||||
|
||||
#include "./backward_references_hq.h"
|
||||
|
||||
#include <string.h> /* memcpy, memset */
|
||||
|
||||
#include "../common/constants.h"
|
||||
#include <brotli/types.h>
|
||||
#include "./command.h"
|
||||
#include "./fast_log.h"
|
||||
#include "./find_match_length.h"
|
||||
#include "./literal_cost.h"
|
||||
#include "./memory.h"
|
||||
#include "./port.h"
|
||||
#include "./prefix.h"
|
||||
#include "./quality.h"
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static const float kInfinity = 1.7e38f; /* ~= 2 ^ 127 */
|
||||
|
||||
void BrotliInitZopfliNodes(ZopfliNode* array, size_t length) {
|
||||
ZopfliNode stub;
|
||||
size_t i;
|
||||
stub.length = 1;
|
||||
stub.distance = 0;
|
||||
stub.insert_length = 0;
|
||||
stub.u.cost = kInfinity;
|
||||
for (i = 0; i < length; ++i) array[i] = stub;
|
||||
}
|
||||
|
||||
static BROTLI_INLINE uint32_t ZopfliNodeCopyLength(const ZopfliNode* self) {
|
||||
return self->length & 0xffffff;
|
||||
}
|
||||
|
||||
static BROTLI_INLINE uint32_t ZopfliNodeLengthCode(const ZopfliNode* self) {
|
||||
const uint32_t modifier = self->length >> 24;
|
||||
return ZopfliNodeCopyLength(self) + 9u - modifier;
|
||||
}
|
||||
|
||||
static BROTLI_INLINE uint32_t ZopfliNodeCopyDistance(const ZopfliNode* self) {
|
||||
return self->distance & 0x1ffffff;
|
||||
}
|
||||
|
||||
static BROTLI_INLINE uint32_t ZopfliNodeDistanceCode(const ZopfliNode* self) {
|
||||
const uint32_t short_code = self->distance >> 25;
|
||||
return short_code == 0 ?
|
||||
ZopfliNodeCopyDistance(self) + BROTLI_NUM_DISTANCE_SHORT_CODES - 1 :
|
||||
short_code - 1;
|
||||
}
|
||||
|
||||
static BROTLI_INLINE uint32_t ZopfliNodeCommandLength(const ZopfliNode* self) {
|
||||
return ZopfliNodeCopyLength(self) + self->insert_length;
|
||||
}
|
||||
|
||||
/* Histogram based cost model for zopflification. */
|
||||
typedef struct ZopfliCostModel {
|
||||
/* The insert and copy length symbols. */
|
||||
float cost_cmd_[BROTLI_NUM_COMMAND_SYMBOLS];
|
||||
float cost_dist_[BROTLI_NUM_DISTANCE_SYMBOLS];
|
||||
/* Cumulative costs of literals per position in the stream. */
|
||||
float* literal_costs_;
|
||||
float min_cost_cmd_;
|
||||
size_t num_bytes_;
|
||||
} ZopfliCostModel;
|
||||
|
||||
static void InitZopfliCostModel(
|
||||
MemoryManager* m, ZopfliCostModel* self, size_t num_bytes) {
|
||||
self->num_bytes_ = num_bytes;
|
||||
self->literal_costs_ = BROTLI_ALLOC(m, float, num_bytes + 2);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
}
|
||||
|
||||
static void CleanupZopfliCostModel(MemoryManager* m, ZopfliCostModel* self) {
|
||||
BROTLI_FREE(m, self->literal_costs_);
|
||||
}
|
||||
|
||||
static void SetCost(const uint32_t* histogram, size_t histogram_size,
|
||||
float* cost) {
|
||||
size_t sum = 0;
|
||||
float log2sum;
|
||||
size_t i;
|
||||
for (i = 0; i < histogram_size; i++) {
|
||||
sum += histogram[i];
|
||||
}
|
||||
log2sum = (float)FastLog2(sum);
|
||||
for (i = 0; i < histogram_size; i++) {
|
||||
if (histogram[i] == 0) {
|
||||
cost[i] = log2sum + 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Shannon bits for this symbol. */
|
||||
cost[i] = log2sum - (float)FastLog2(histogram[i]);
|
||||
|
||||
/* Cannot be coded with less than 1 bit */
|
||||
if (cost[i] < 1) cost[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void ZopfliCostModelSetFromCommands(ZopfliCostModel* self,
|
||||
size_t position,
|
||||
const uint8_t* ringbuffer,
|
||||
size_t ringbuffer_mask,
|
||||
const Command* commands,
|
||||
size_t num_commands,
|
||||
size_t last_insert_len) {
|
||||
uint32_t histogram_literal[BROTLI_NUM_LITERAL_SYMBOLS];
|
||||
uint32_t histogram_cmd[BROTLI_NUM_COMMAND_SYMBOLS];
|
||||
uint32_t histogram_dist[BROTLI_NUM_DISTANCE_SYMBOLS];
|
||||
float cost_literal[BROTLI_NUM_LITERAL_SYMBOLS];
|
||||
size_t pos = position - last_insert_len;
|
||||
float min_cost_cmd = kInfinity;
|
||||
size_t i;
|
||||
float* cost_cmd = self->cost_cmd_;
|
||||
|
||||
memset(histogram_literal, 0, sizeof(histogram_literal));
|
||||
memset(histogram_cmd, 0, sizeof(histogram_cmd));
|
||||
memset(histogram_dist, 0, sizeof(histogram_dist));
|
||||
|
||||
for (i = 0; i < num_commands; i++) {
|
||||
size_t inslength = commands[i].insert_len_;
|
||||
size_t copylength = CommandCopyLen(&commands[i]);
|
||||
size_t distcode = commands[i].dist_prefix_;
|
||||
size_t cmdcode = commands[i].cmd_prefix_;
|
||||
size_t j;
|
||||
|
||||
histogram_cmd[cmdcode]++;
|
||||
if (cmdcode >= 128) histogram_dist[distcode]++;
|
||||
|
||||
for (j = 0; j < inslength; j++) {
|
||||
histogram_literal[ringbuffer[(pos + j) & ringbuffer_mask]]++;
|
||||
}
|
||||
|
||||
pos += inslength + copylength;
|
||||
}
|
||||
|
||||
SetCost(histogram_literal, BROTLI_NUM_LITERAL_SYMBOLS, cost_literal);
|
||||
SetCost(histogram_cmd, BROTLI_NUM_COMMAND_SYMBOLS, cost_cmd);
|
||||
SetCost(histogram_dist, BROTLI_NUM_DISTANCE_SYMBOLS, self->cost_dist_);
|
||||
|
||||
for (i = 0; i < BROTLI_NUM_COMMAND_SYMBOLS; ++i) {
|
||||
min_cost_cmd = BROTLI_MIN(float, min_cost_cmd, cost_cmd[i]);
|
||||
}
|
||||
self->min_cost_cmd_ = min_cost_cmd;
|
||||
|
||||
{
|
||||
float* literal_costs = self->literal_costs_;
|
||||
size_t num_bytes = self->num_bytes_;
|
||||
literal_costs[0] = 0.0;
|
||||
for (i = 0; i < num_bytes; ++i) {
|
||||
literal_costs[i + 1] = literal_costs[i] +
|
||||
cost_literal[ringbuffer[(position + i) & ringbuffer_mask]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ZopfliCostModelSetFromLiteralCosts(ZopfliCostModel* self,
|
||||
size_t position,
|
||||
const uint8_t* ringbuffer,
|
||||
size_t ringbuffer_mask) {
|
||||
float* literal_costs = self->literal_costs_;
|
||||
float* cost_dist = self->cost_dist_;
|
||||
float* cost_cmd = self->cost_cmd_;
|
||||
size_t num_bytes = self->num_bytes_;
|
||||
size_t i;
|
||||
BrotliEstimateBitCostsForLiterals(position, num_bytes, ringbuffer_mask,
|
||||
ringbuffer, &literal_costs[1]);
|
||||
literal_costs[0] = 0.0;
|
||||
for (i = 0; i < num_bytes; ++i) {
|
||||
literal_costs[i + 1] += literal_costs[i];
|
||||
}
|
||||
for (i = 0; i < BROTLI_NUM_COMMAND_SYMBOLS; ++i) {
|
||||
cost_cmd[i] = (float)FastLog2(11 + (uint32_t)i);
|
||||
}
|
||||
for (i = 0; i < BROTLI_NUM_DISTANCE_SYMBOLS; ++i) {
|
||||
cost_dist[i] = (float)FastLog2(20 + (uint32_t)i);
|
||||
}
|
||||
self->min_cost_cmd_ = (float)FastLog2(11);
|
||||
}
|
||||
|
||||
static BROTLI_INLINE float ZopfliCostModelGetCommandCost(
|
||||
const ZopfliCostModel* self, uint16_t cmdcode) {
|
||||
return self->cost_cmd_[cmdcode];
|
||||
}
|
||||
|
||||
static BROTLI_INLINE float ZopfliCostModelGetDistanceCost(
|
||||
const ZopfliCostModel* self, size_t distcode) {
|
||||
return self->cost_dist_[distcode];
|
||||
}
|
||||
|
||||
static BROTLI_INLINE float ZopfliCostModelGetLiteralCosts(
|
||||
const ZopfliCostModel* self, size_t from, size_t to) {
|
||||
return self->literal_costs_[to] - self->literal_costs_[from];
|
||||
}
|
||||
|
||||
static BROTLI_INLINE float ZopfliCostModelGetMinCostCmd(
|
||||
const ZopfliCostModel* self) {
|
||||
return self->min_cost_cmd_;
|
||||
}
|
||||
|
||||
/* REQUIRES: len >= 2, start_pos <= pos */
|
||||
/* REQUIRES: cost < kInfinity, nodes[start_pos].cost < kInfinity */
|
||||
/* Maintains the "ZopfliNode array invariant". */
|
||||
static BROTLI_INLINE void UpdateZopfliNode(ZopfliNode* nodes, size_t pos,
|
||||
size_t start_pos, size_t len, size_t len_code, size_t dist,
|
||||
size_t short_code, float cost) {
|
||||
ZopfliNode* next = &nodes[pos + len];
|
||||
next->length = (uint32_t)(len | ((len + 9u - len_code) << 24));
|
||||
next->distance = (uint32_t)(dist | (short_code << 25));
|
||||
next->insert_length = (uint32_t)(pos - start_pos);
|
||||
next->u.cost = cost;
|
||||
}
|
||||
|
||||
typedef struct PosData {
|
||||
size_t pos;
|
||||
int distance_cache[4];
|
||||
float costdiff;
|
||||
float cost;
|
||||
} PosData;
|
||||
|
||||
/* Maintains the smallest 8 cost difference together with their positions */
|
||||
typedef struct StartPosQueue {
|
||||
PosData q_[8];
|
||||
size_t idx_;
|
||||
} StartPosQueue;
|
||||
|
||||
static BROTLI_INLINE void InitStartPosQueue(StartPosQueue* self) {
|
||||
self->idx_ = 0;
|
||||
}
|
||||
|
||||
static size_t StartPosQueueSize(const StartPosQueue* self) {
|
||||
return BROTLI_MIN(size_t, self->idx_, 8);
|
||||
}
|
||||
|
||||
static void StartPosQueuePush(StartPosQueue* self, const PosData* posdata) {
|
||||
size_t offset = ~(self->idx_++) & 7;
|
||||
size_t len = StartPosQueueSize(self);
|
||||
size_t i;
|
||||
PosData* q = self->q_;
|
||||
q[offset] = *posdata;
|
||||
/* Restore the sorted order. In the list of |len| items at most |len - 1|
|
||||
adjacent element comparisons / swaps are required. */
|
||||
for (i = 1; i < len; ++i) {
|
||||
if (q[offset & 7].costdiff > q[(offset + 1) & 7].costdiff) {
|
||||
BROTLI_SWAP(PosData, q, offset & 7, (offset + 1) & 7);
|
||||
}
|
||||
++offset;
|
||||
}
|
||||
}
|
||||
|
||||
static const PosData* StartPosQueueAt(const StartPosQueue* self, size_t k) {
|
||||
return &self->q_[(k - self->idx_) & 7];
|
||||
}
|
||||
|
||||
/* Returns the minimum possible copy length that can improve the cost of any */
|
||||
/* future position. */
|
||||
static size_t ComputeMinimumCopyLength(const float start_cost,
|
||||
const ZopfliNode* nodes,
|
||||
const size_t num_bytes,
|
||||
const size_t pos) {
|
||||
/* Compute the minimum possible cost of reaching any future position. */
|
||||
float min_cost = start_cost;
|
||||
size_t len = 2;
|
||||
size_t next_len_bucket = 4;
|
||||
size_t next_len_offset = 10;
|
||||
while (pos + len <= num_bytes && nodes[pos + len].u.cost <= min_cost) {
|
||||
/* We already reached (pos + len) with no more cost than the minimum
|
||||
possible cost of reaching anything from this pos, so there is no point in
|
||||
looking for lengths <= len. */
|
||||
++len;
|
||||
if (len == next_len_offset) {
|
||||
/* We reached the next copy length code bucket, so we add one more
|
||||
extra bit to the minimum cost. */
|
||||
min_cost += 1.0f;
|
||||
next_len_offset += next_len_bucket;
|
||||
next_len_bucket *= 2;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/* REQUIRES: nodes[pos].cost < kInfinity
|
||||
REQUIRES: nodes[0..pos] satisfies that "ZopfliNode array invariant". */
|
||||
static uint32_t ComputeDistanceShortcut(const size_t block_start,
|
||||
const size_t pos,
|
||||
const size_t max_backward,
|
||||
const ZopfliNode* nodes) {
|
||||
const size_t clen = ZopfliNodeCopyLength(&nodes[pos]);
|
||||
const size_t ilen = nodes[pos].insert_length;
|
||||
const size_t dist = ZopfliNodeCopyDistance(&nodes[pos]);
|
||||
/* Since |block_start + pos| is the end position of the command, the copy part
|
||||
starts from |block_start + pos - clen|. Distances that are greater than
|
||||
this or greater than |max_backward| are static dictionary references, and
|
||||
do not update the last distances. Also distance code 0 (last distance)
|
||||
does not update the last distances. */
|
||||
if (pos == 0) {
|
||||
return 0;
|
||||
} else if (dist + clen <= block_start + pos &&
|
||||
dist <= max_backward &&
|
||||
ZopfliNodeDistanceCode(&nodes[pos]) > 0) {
|
||||
return (uint32_t)pos;
|
||||
} else {
|
||||
return nodes[pos - clen - ilen].u.shortcut;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fills in dist_cache[0..3] with the last four distances (as defined by
|
||||
Section 4. of the Spec) that would be used at (block_start + pos) if we
|
||||
used the shortest path of commands from block_start, computed from
|
||||
nodes[0..pos]. The last four distances at block_start are in
|
||||
starting_dist_cache[0..3].
|
||||
REQUIRES: nodes[pos].cost < kInfinity
|
||||
REQUIRES: nodes[0..pos] satisfies that "ZopfliNode array invariant". */
|
||||
static void ComputeDistanceCache(const size_t pos,
|
||||
const int* starting_dist_cache,
|
||||
const ZopfliNode* nodes,
|
||||
int* dist_cache) {
|
||||
int idx = 0;
|
||||
size_t p = nodes[pos].u.shortcut;
|
||||
while (idx < 4 && p > 0) {
|
||||
const size_t ilen = nodes[p].insert_length;
|
||||
const size_t clen = ZopfliNodeCopyLength(&nodes[p]);
|
||||
const size_t dist = ZopfliNodeCopyDistance(&nodes[p]);
|
||||
dist_cache[idx++] = (int)dist;
|
||||
/* Because of prerequisite, p >= clen + ilen >= 2. */
|
||||
p = nodes[p - clen - ilen].u.shortcut;
|
||||
}
|
||||
for (; idx < 4; ++idx) {
|
||||
dist_cache[idx] = *starting_dist_cache++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Maintains "ZopfliNode array invariant" and pushes node to the queue, if it
|
||||
is eligible. */
|
||||
static void EvaluateNode(
|
||||
const size_t block_start, const size_t pos, const size_t max_backward_limit,
|
||||
const int* starting_dist_cache, const ZopfliCostModel* model,
|
||||
StartPosQueue* queue, ZopfliNode* nodes) {
|
||||
/* Save cost, because ComputeDistanceCache invalidates it. */
|
||||
float node_cost = nodes[pos].u.cost;
|
||||
nodes[pos].u.shortcut = ComputeDistanceShortcut(
|
||||
block_start, pos, max_backward_limit, nodes);
|
||||
if (node_cost <= ZopfliCostModelGetLiteralCosts(model, 0, pos)) {
|
||||
PosData posdata;
|
||||
posdata.pos = pos;
|
||||
posdata.cost = node_cost;
|
||||
posdata.costdiff = node_cost -
|
||||
ZopfliCostModelGetLiteralCosts(model, 0, pos);
|
||||
ComputeDistanceCache(
|
||||
pos, starting_dist_cache, nodes, posdata.distance_cache);
|
||||
StartPosQueuePush(queue, &posdata);
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns longest copy length. */
|
||||
static size_t UpdateNodes(
|
||||
const size_t num_bytes, const size_t block_start, const size_t pos,
|
||||
const uint8_t* ringbuffer, const size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params, const size_t max_backward_limit,
|
||||
const int* starting_dist_cache, const size_t num_matches,
|
||||
const BackwardMatch* matches, const ZopfliCostModel* model,
|
||||
StartPosQueue* queue, ZopfliNode* nodes) {
|
||||
const size_t cur_ix = block_start + pos;
|
||||
const size_t cur_ix_masked = cur_ix & ringbuffer_mask;
|
||||
const size_t max_distance = BROTLI_MIN(size_t, cur_ix, max_backward_limit);
|
||||
const size_t max_len = num_bytes - pos;
|
||||
const size_t max_zopfli_len = MaxZopfliLen(params);
|
||||
const size_t max_iters = MaxZopfliCandidates(params);
|
||||
size_t min_len;
|
||||
size_t result = 0;
|
||||
size_t k;
|
||||
|
||||
EvaluateNode(block_start, pos, max_backward_limit, starting_dist_cache, model,
|
||||
queue, nodes);
|
||||
|
||||
{
|
||||
const PosData* posdata = StartPosQueueAt(queue, 0);
|
||||
float min_cost = (posdata->cost + ZopfliCostModelGetMinCostCmd(model) +
|
||||
ZopfliCostModelGetLiteralCosts(model, posdata->pos, pos));
|
||||
min_len = ComputeMinimumCopyLength(min_cost, nodes, num_bytes, pos);
|
||||
}
|
||||
|
||||
/* Go over the command starting positions in order of increasing cost
|
||||
difference. */
|
||||
for (k = 0; k < max_iters && k < StartPosQueueSize(queue); ++k) {
|
||||
const PosData* posdata = StartPosQueueAt(queue, k);
|
||||
const size_t start = posdata->pos;
|
||||
const uint16_t inscode = GetInsertLengthCode(pos - start);
|
||||
const float start_costdiff = posdata->costdiff;
|
||||
const float base_cost = start_costdiff + (float)GetInsertExtra(inscode) +
|
||||
ZopfliCostModelGetLiteralCosts(model, 0, pos);
|
||||
|
||||
/* Look for last distance matches using the distance cache from this
|
||||
starting position. */
|
||||
size_t best_len = min_len - 1;
|
||||
size_t j = 0;
|
||||
for (; j < BROTLI_NUM_DISTANCE_SHORT_CODES && best_len < max_len; ++j) {
|
||||
const size_t idx = kDistanceCacheIndex[j];
|
||||
const size_t backward =
|
||||
(size_t)(posdata->distance_cache[idx] + kDistanceCacheOffset[j]);
|
||||
size_t prev_ix = cur_ix - backward;
|
||||
if (prev_ix >= cur_ix) {
|
||||
continue;
|
||||
}
|
||||
if (BROTLI_PREDICT_FALSE(backward > max_distance)) {
|
||||
continue;
|
||||
}
|
||||
prev_ix &= ringbuffer_mask;
|
||||
|
||||
if (cur_ix_masked + best_len > ringbuffer_mask ||
|
||||
prev_ix + best_len > ringbuffer_mask ||
|
||||
ringbuffer[cur_ix_masked + best_len] !=
|
||||
ringbuffer[prev_ix + best_len]) {
|
||||
continue;
|
||||
}
|
||||
{
|
||||
const size_t len =
|
||||
FindMatchLengthWithLimit(&ringbuffer[prev_ix],
|
||||
&ringbuffer[cur_ix_masked],
|
||||
max_len);
|
||||
const float dist_cost = base_cost +
|
||||
ZopfliCostModelGetDistanceCost(model, j);
|
||||
size_t l;
|
||||
for (l = best_len + 1; l <= len; ++l) {
|
||||
const uint16_t copycode = GetCopyLengthCode(l);
|
||||
const uint16_t cmdcode =
|
||||
CombineLengthCodes(inscode, copycode, j == 0);
|
||||
const float cost = (cmdcode < 128 ? base_cost : dist_cost) +
|
||||
(float)GetCopyExtra(copycode) +
|
||||
ZopfliCostModelGetCommandCost(model, cmdcode);
|
||||
if (cost < nodes[pos + l].u.cost) {
|
||||
UpdateZopfliNode(nodes, pos, start, l, l, backward, j + 1, cost);
|
||||
result = BROTLI_MAX(size_t, result, l);
|
||||
}
|
||||
best_len = l;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* At higher iterations look only for new last distance matches, since
|
||||
looking only for new command start positions with the same distances
|
||||
does not help much. */
|
||||
if (k >= 2) continue;
|
||||
|
||||
{
|
||||
/* Loop through all possible copy lengths at this position. */
|
||||
size_t len = min_len;
|
||||
for (j = 0; j < num_matches; ++j) {
|
||||
BackwardMatch match = matches[j];
|
||||
size_t dist = match.distance;
|
||||
BROTLI_BOOL is_dictionary_match = TO_BROTLI_BOOL(dist > max_distance);
|
||||
/* We already tried all possible last distance matches, so we can use
|
||||
normal distance code here. */
|
||||
size_t dist_code = dist + BROTLI_NUM_DISTANCE_SHORT_CODES - 1;
|
||||
uint16_t dist_symbol;
|
||||
uint32_t distextra;
|
||||
uint32_t distnumextra;
|
||||
float dist_cost;
|
||||
size_t max_match_len;
|
||||
PrefixEncodeCopyDistance(dist_code, 0, 0, &dist_symbol, &distextra);
|
||||
distnumextra = distextra >> 24;
|
||||
dist_cost = base_cost + (float)distnumextra +
|
||||
ZopfliCostModelGetDistanceCost(model, dist_symbol);
|
||||
|
||||
/* Try all copy lengths up until the maximum copy length corresponding
|
||||
to this distance. If the distance refers to the static dictionary, or
|
||||
the maximum length is long enough, try only one maximum length. */
|
||||
max_match_len = BackwardMatchLength(&match);
|
||||
if (len < max_match_len &&
|
||||
(is_dictionary_match || max_match_len > max_zopfli_len)) {
|
||||
len = max_match_len;
|
||||
}
|
||||
for (; len <= max_match_len; ++len) {
|
||||
const size_t len_code =
|
||||
is_dictionary_match ? BackwardMatchLengthCode(&match) : len;
|
||||
const uint16_t copycode = GetCopyLengthCode(len_code);
|
||||
const uint16_t cmdcode = CombineLengthCodes(inscode, copycode, 0);
|
||||
const float cost = dist_cost + (float)GetCopyExtra(copycode) +
|
||||
ZopfliCostModelGetCommandCost(model, cmdcode);
|
||||
if (cost < nodes[pos + len].u.cost) {
|
||||
UpdateZopfliNode(nodes, pos, start, len, len_code, dist, 0, cost);
|
||||
result = BROTLI_MAX(size_t, result, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static size_t ComputeShortestPathFromNodes(size_t num_bytes,
|
||||
ZopfliNode* nodes) {
|
||||
size_t index = num_bytes;
|
||||
size_t num_commands = 0;
|
||||
while (nodes[index].insert_length == 0 && nodes[index].length == 1) --index;
|
||||
nodes[index].u.next = BROTLI_UINT32_MAX;
|
||||
while (index != 0) {
|
||||
size_t len = ZopfliNodeCommandLength(&nodes[index]);
|
||||
index -= len;
|
||||
nodes[index].u.next = (uint32_t)len;
|
||||
num_commands++;
|
||||
}
|
||||
return num_commands;
|
||||
}
|
||||
|
||||
void BrotliZopfliCreateCommands(const size_t num_bytes,
|
||||
const size_t block_start,
|
||||
const size_t max_backward_limit,
|
||||
const ZopfliNode* nodes,
|
||||
int* dist_cache,
|
||||
size_t* last_insert_len,
|
||||
Command* commands,
|
||||
size_t* num_literals) {
|
||||
size_t pos = 0;
|
||||
uint32_t offset = nodes[0].u.next;
|
||||
size_t i;
|
||||
for (i = 0; offset != BROTLI_UINT32_MAX; i++) {
|
||||
const ZopfliNode* next = &nodes[pos + offset];
|
||||
size_t copy_length = ZopfliNodeCopyLength(next);
|
||||
size_t insert_length = next->insert_length;
|
||||
pos += insert_length;
|
||||
offset = next->u.next;
|
||||
if (i == 0) {
|
||||
insert_length += *last_insert_len;
|
||||
*last_insert_len = 0;
|
||||
}
|
||||
{
|
||||
size_t distance = ZopfliNodeCopyDistance(next);
|
||||
size_t len_code = ZopfliNodeLengthCode(next);
|
||||
size_t max_distance =
|
||||
BROTLI_MIN(size_t, block_start + pos, max_backward_limit);
|
||||
BROTLI_BOOL is_dictionary = TO_BROTLI_BOOL(distance > max_distance);
|
||||
size_t dist_code = ZopfliNodeDistanceCode(next);
|
||||
|
||||
InitCommand(
|
||||
&commands[i], insert_length, copy_length, len_code, dist_code);
|
||||
|
||||
if (!is_dictionary && dist_code > 0) {
|
||||
dist_cache[3] = dist_cache[2];
|
||||
dist_cache[2] = dist_cache[1];
|
||||
dist_cache[1] = dist_cache[0];
|
||||
dist_cache[0] = (int)distance;
|
||||
}
|
||||
}
|
||||
|
||||
*num_literals += insert_length;
|
||||
pos += copy_length;
|
||||
}
|
||||
*last_insert_len += num_bytes - pos;
|
||||
}
|
||||
|
||||
static size_t ZopfliIterate(size_t num_bytes,
|
||||
size_t position,
|
||||
const uint8_t* ringbuffer,
|
||||
size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params,
|
||||
const size_t max_backward_limit,
|
||||
const int* dist_cache,
|
||||
const ZopfliCostModel* model,
|
||||
const uint32_t* num_matches,
|
||||
const BackwardMatch* matches,
|
||||
ZopfliNode* nodes) {
|
||||
const size_t max_zopfli_len = MaxZopfliLen(params);
|
||||
StartPosQueue queue;
|
||||
size_t cur_match_pos = 0;
|
||||
size_t i;
|
||||
nodes[0].length = 0;
|
||||
nodes[0].u.cost = 0;
|
||||
InitStartPosQueue(&queue);
|
||||
for (i = 0; i + 3 < num_bytes; i++) {
|
||||
size_t skip = UpdateNodes(num_bytes, position, i, ringbuffer,
|
||||
ringbuffer_mask, params, max_backward_limit, dist_cache,
|
||||
num_matches[i], &matches[cur_match_pos], model, &queue, nodes);
|
||||
if (skip < BROTLI_LONG_COPY_QUICK_STEP) skip = 0;
|
||||
cur_match_pos += num_matches[i];
|
||||
if (num_matches[i] == 1 &&
|
||||
BackwardMatchLength(&matches[cur_match_pos - 1]) > max_zopfli_len) {
|
||||
skip = BROTLI_MAX(size_t,
|
||||
BackwardMatchLength(&matches[cur_match_pos - 1]), skip);
|
||||
}
|
||||
if (skip > 1) {
|
||||
skip--;
|
||||
while (skip) {
|
||||
i++;
|
||||
if (i + 3 >= num_bytes) break;
|
||||
EvaluateNode(
|
||||
position, i, max_backward_limit, dist_cache, model, &queue, nodes);
|
||||
cur_match_pos += num_matches[i];
|
||||
skip--;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ComputeShortestPathFromNodes(num_bytes, nodes);
|
||||
}
|
||||
|
||||
|
||||
size_t BrotliZopfliComputeShortestPath(MemoryManager* m,
|
||||
size_t num_bytes,
|
||||
size_t position,
|
||||
const uint8_t* ringbuffer,
|
||||
size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params,
|
||||
const size_t max_backward_limit,
|
||||
const int* dist_cache,
|
||||
H10* hasher,
|
||||
ZopfliNode* nodes) {
|
||||
const size_t max_zopfli_len = MaxZopfliLen(params);
|
||||
ZopfliCostModel model;
|
||||
StartPosQueue queue;
|
||||
BackwardMatch matches[MAX_NUM_MATCHES_H10];
|
||||
const size_t store_end = num_bytes >= StoreLookaheadH10() ?
|
||||
position + num_bytes - StoreLookaheadH10() + 1 : position;
|
||||
size_t i;
|
||||
nodes[0].length = 0;
|
||||
nodes[0].u.cost = 0;
|
||||
InitZopfliCostModel(m, &model, num_bytes);
|
||||
if (BROTLI_IS_OOM(m)) return 0;
|
||||
ZopfliCostModelSetFromLiteralCosts(
|
||||
&model, position, ringbuffer, ringbuffer_mask);
|
||||
InitStartPosQueue(&queue);
|
||||
for (i = 0; i + HashTypeLengthH10() - 1 < num_bytes; i++) {
|
||||
const size_t pos = position + i;
|
||||
const size_t max_distance = BROTLI_MIN(size_t, pos, max_backward_limit);
|
||||
size_t num_matches = FindAllMatchesH10(hasher, ringbuffer, ringbuffer_mask,
|
||||
pos, num_bytes - i, max_distance, params, matches);
|
||||
size_t skip;
|
||||
if (num_matches > 0 &&
|
||||
BackwardMatchLength(&matches[num_matches - 1]) > max_zopfli_len) {
|
||||
matches[0] = matches[num_matches - 1];
|
||||
num_matches = 1;
|
||||
}
|
||||
skip = UpdateNodes(num_bytes, position, i, ringbuffer, ringbuffer_mask,
|
||||
params, max_backward_limit, dist_cache, num_matches, matches, &model,
|
||||
&queue, nodes);
|
||||
if (skip < BROTLI_LONG_COPY_QUICK_STEP) skip = 0;
|
||||
if (num_matches == 1 && BackwardMatchLength(&matches[0]) > max_zopfli_len) {
|
||||
skip = BROTLI_MAX(size_t, BackwardMatchLength(&matches[0]), skip);
|
||||
}
|
||||
if (skip > 1) {
|
||||
/* Add the tail of the copy to the hasher. */
|
||||
StoreRangeH10(hasher, ringbuffer, ringbuffer_mask, pos + 1, BROTLI_MIN(
|
||||
size_t, pos + skip, store_end));
|
||||
skip--;
|
||||
while (skip) {
|
||||
i++;
|
||||
if (i + HashTypeLengthH10() - 1 >= num_bytes) break;
|
||||
EvaluateNode(
|
||||
position, i, max_backward_limit, dist_cache, &model, &queue, nodes);
|
||||
skip--;
|
||||
}
|
||||
}
|
||||
}
|
||||
CleanupZopfliCostModel(m, &model);
|
||||
return ComputeShortestPathFromNodes(num_bytes, nodes);
|
||||
}
|
||||
|
||||
void BrotliCreateZopfliBackwardReferences(
|
||||
MemoryManager* m, size_t num_bytes, size_t position,
|
||||
const uint8_t* ringbuffer, size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params, H10* hasher, int* dist_cache,
|
||||
size_t* last_insert_len, Command* commands, size_t* num_commands,
|
||||
size_t* num_literals) {
|
||||
const size_t max_backward_limit = BROTLI_MAX_BACKWARD_LIMIT(params->lgwin);
|
||||
ZopfliNode* nodes;
|
||||
nodes = BROTLI_ALLOC(m, ZopfliNode, num_bytes + 1);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
BrotliInitZopfliNodes(nodes, num_bytes + 1);
|
||||
*num_commands += BrotliZopfliComputeShortestPath(m, num_bytes, position,
|
||||
ringbuffer, ringbuffer_mask, params, max_backward_limit,
|
||||
dist_cache, hasher, nodes);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
BrotliZopfliCreateCommands(num_bytes, position, max_backward_limit, nodes,
|
||||
dist_cache, last_insert_len, commands, num_literals);
|
||||
BROTLI_FREE(m, nodes);
|
||||
}
|
||||
|
||||
void BrotliCreateHqZopfliBackwardReferences(
|
||||
MemoryManager* m, size_t num_bytes, size_t position,
|
||||
const uint8_t* ringbuffer, size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params, H10* hasher, int* dist_cache,
|
||||
size_t* last_insert_len, Command* commands, size_t* num_commands,
|
||||
size_t* num_literals) {
|
||||
const size_t max_backward_limit = BROTLI_MAX_BACKWARD_LIMIT(params->lgwin);
|
||||
uint32_t* num_matches = BROTLI_ALLOC(m, uint32_t, num_bytes);
|
||||
size_t matches_size = 4 * num_bytes;
|
||||
const size_t store_end = num_bytes >= StoreLookaheadH10() ?
|
||||
position + num_bytes - StoreLookaheadH10() + 1 : position;
|
||||
size_t cur_match_pos = 0;
|
||||
size_t i;
|
||||
size_t orig_num_literals;
|
||||
size_t orig_last_insert_len;
|
||||
int orig_dist_cache[4];
|
||||
size_t orig_num_commands;
|
||||
ZopfliCostModel model;
|
||||
ZopfliNode* nodes;
|
||||
BackwardMatch* matches = BROTLI_ALLOC(m, BackwardMatch, matches_size);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
for (i = 0; i + HashTypeLengthH10() - 1 < num_bytes; ++i) {
|
||||
const size_t pos = position + i;
|
||||
size_t max_distance = BROTLI_MIN(size_t, pos, max_backward_limit);
|
||||
size_t max_length = num_bytes - i;
|
||||
size_t num_found_matches;
|
||||
size_t cur_match_end;
|
||||
size_t j;
|
||||
/* Ensure that we have enough free slots. */
|
||||
BROTLI_ENSURE_CAPACITY(m, BackwardMatch, matches, matches_size,
|
||||
cur_match_pos + MAX_NUM_MATCHES_H10);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
num_found_matches = FindAllMatchesH10(hasher, ringbuffer, ringbuffer_mask,
|
||||
pos, max_length, max_distance, params, &matches[cur_match_pos]);
|
||||
cur_match_end = cur_match_pos + num_found_matches;
|
||||
for (j = cur_match_pos; j + 1 < cur_match_end; ++j) {
|
||||
assert(BackwardMatchLength(&matches[j]) <
|
||||
BackwardMatchLength(&matches[j + 1]));
|
||||
assert(matches[j].distance > max_distance ||
|
||||
matches[j].distance <= matches[j + 1].distance);
|
||||
}
|
||||
num_matches[i] = (uint32_t)num_found_matches;
|
||||
if (num_found_matches > 0) {
|
||||
const size_t match_len = BackwardMatchLength(&matches[cur_match_end - 1]);
|
||||
if (match_len > MAX_ZOPFLI_LEN_QUALITY_11) {
|
||||
const size_t skip = match_len - 1;
|
||||
matches[cur_match_pos++] = matches[cur_match_end - 1];
|
||||
num_matches[i] = 1;
|
||||
/* Add the tail of the copy to the hasher. */
|
||||
StoreRangeH10(hasher, ringbuffer, ringbuffer_mask, pos + 1,
|
||||
BROTLI_MIN(size_t, pos + match_len, store_end));
|
||||
memset(&num_matches[i + 1], 0, skip * sizeof(num_matches[0]));
|
||||
i += skip;
|
||||
} else {
|
||||
cur_match_pos = cur_match_end;
|
||||
}
|
||||
}
|
||||
}
|
||||
orig_num_literals = *num_literals;
|
||||
orig_last_insert_len = *last_insert_len;
|
||||
memcpy(orig_dist_cache, dist_cache, 4 * sizeof(dist_cache[0]));
|
||||
orig_num_commands = *num_commands;
|
||||
nodes = BROTLI_ALLOC(m, ZopfliNode, num_bytes + 1);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
InitZopfliCostModel(m, &model, num_bytes);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
for (i = 0; i < 2; i++) {
|
||||
BrotliInitZopfliNodes(nodes, num_bytes + 1);
|
||||
if (i == 0) {
|
||||
ZopfliCostModelSetFromLiteralCosts(
|
||||
&model, position, ringbuffer, ringbuffer_mask);
|
||||
} else {
|
||||
ZopfliCostModelSetFromCommands(&model, position, ringbuffer,
|
||||
ringbuffer_mask, commands, *num_commands - orig_num_commands,
|
||||
orig_last_insert_len);
|
||||
}
|
||||
*num_commands = orig_num_commands;
|
||||
*num_literals = orig_num_literals;
|
||||
*last_insert_len = orig_last_insert_len;
|
||||
memcpy(dist_cache, orig_dist_cache, 4 * sizeof(dist_cache[0]));
|
||||
*num_commands += ZopfliIterate(num_bytes, position, ringbuffer,
|
||||
ringbuffer_mask, params, max_backward_limit, dist_cache,
|
||||
&model, num_matches, matches, nodes);
|
||||
BrotliZopfliCreateCommands(num_bytes, position, max_backward_limit,
|
||||
nodes, dist_cache, last_insert_len, commands, num_literals);
|
||||
}
|
||||
CleanupZopfliCostModel(m, &model);
|
||||
BROTLI_FREE(m, nodes);
|
||||
BROTLI_FREE(m, matches);
|
||||
BROTLI_FREE(m, num_matches);
|
||||
}
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
} /* extern "C" */
|
||||
#endif
|
98
enc/backward_references_hq.h
Executable file
98
enc/backward_references_hq.h
Executable file
@ -0,0 +1,98 @@
|
||||
/* Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
Distributed under MIT license.
|
||||
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/* Function to find backward reference copies. */
|
||||
|
||||
#ifndef BROTLI_ENC_BACKWARD_REFERENCES_HQ_H_
|
||||
#define BROTLI_ENC_BACKWARD_REFERENCES_HQ_H_
|
||||
|
||||
#include "../common/constants.h"
|
||||
#include <brotli/types.h>
|
||||
#include "./command.h"
|
||||
#include "./hash.h"
|
||||
#include "./memory.h"
|
||||
#include "./port.h"
|
||||
#include "./quality.h"
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
BROTLI_INTERNAL void BrotliCreateZopfliBackwardReferences(
|
||||
MemoryManager* m, size_t num_bytes, size_t position,
|
||||
const uint8_t* ringbuffer, size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params, H10* hasher, int* dist_cache,
|
||||
size_t* last_insert_len, Command* commands, size_t* num_commands,
|
||||
size_t* num_literals);
|
||||
|
||||
BROTLI_INTERNAL void BrotliCreateHqZopfliBackwardReferences(
|
||||
MemoryManager* m, size_t num_bytes, size_t position,
|
||||
const uint8_t* ringbuffer, size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params, H10* hasher, int* dist_cache,
|
||||
size_t* last_insert_len, Command* commands, size_t* num_commands,
|
||||
size_t* num_literals);
|
||||
|
||||
typedef struct ZopfliNode {
|
||||
/* best length to get up to this byte (not including this byte itself)
|
||||
highest 8 bit is used to reconstruct the length code */
|
||||
uint32_t length;
|
||||
/* distance associated with the length
|
||||
highest 7 bit contains distance short code + 1 (or zero if no short code)
|
||||
*/
|
||||
uint32_t distance;
|
||||
/* number of literal inserts before this copy */
|
||||
uint32_t insert_length;
|
||||
|
||||
/* This union holds information used by dynamic-programming. During forward
|
||||
pass |cost| it used to store the goal function. When node is processed its
|
||||
|cost| is invalidated in favor of |shortcut|. On path back-tracing pass
|
||||
|next| is assigned the offset to next node on the path. */
|
||||
union {
|
||||
/* Smallest cost to get to this byte from the beginning, as found so far. */
|
||||
float cost;
|
||||
/* Offset to the next node on the path. Equals to command_length() of the
|
||||
next node on the path. For last node equals to BROTLI_UINT32_MAX */
|
||||
uint32_t next;
|
||||
/* Node position that provides next distance for distance cache. */
|
||||
uint32_t shortcut;
|
||||
} u;
|
||||
} ZopfliNode;
|
||||
|
||||
BROTLI_INTERNAL void BrotliInitZopfliNodes(ZopfliNode* array, size_t length);
|
||||
|
||||
/* Computes the shortest path of commands from position to at most
|
||||
position + num_bytes.
|
||||
|
||||
On return, path->size() is the number of commands found and path[i] is the
|
||||
length of the i-th command (copy length plus insert length).
|
||||
Note that the sum of the lengths of all commands can be less than num_bytes.
|
||||
|
||||
On return, the nodes[0..num_bytes] array will have the following
|
||||
"ZopfliNode array invariant":
|
||||
For each i in [1..num_bytes], if nodes[i].cost < kInfinity, then
|
||||
(1) nodes[i].copy_length() >= 2
|
||||
(2) nodes[i].command_length() <= i and
|
||||
(3) nodes[i - nodes[i].command_length()].cost < kInfinity */
|
||||
BROTLI_INTERNAL size_t BrotliZopfliComputeShortestPath(
|
||||
MemoryManager* m, size_t num_bytes, size_t position,
|
||||
const uint8_t* ringbuffer, size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params, const size_t max_backward_limit,
|
||||
const int* dist_cache, H10* hasher, ZopfliNode* nodes);
|
||||
|
||||
BROTLI_INTERNAL void BrotliZopfliCreateCommands(const size_t num_bytes,
|
||||
const size_t block_start,
|
||||
const size_t max_backward_limit,
|
||||
const ZopfliNode* nodes,
|
||||
int* dist_cache,
|
||||
size_t* last_insert_len,
|
||||
Command* commands,
|
||||
size_t* num_literals);
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* BROTLI_ENC_BACKWARD_REFERENCES_HQ_H_ */
|
@ -10,13 +10,13 @@
|
||||
#define Hasher HASHER()
|
||||
|
||||
static BROTLI_NOINLINE void FN(CreateBackwardReferences)(
|
||||
MemoryManager* m, size_t num_bytes, size_t position, BROTLI_BOOL is_last,
|
||||
size_t num_bytes, size_t position,
|
||||
const uint8_t* ringbuffer, size_t ringbuffer_mask,
|
||||
const BrotliEncoderParams* params, Hasher* hasher, int* dist_cache,
|
||||
size_t* last_insert_len, Command* commands, size_t* num_commands,
|
||||
size_t* num_literals) {
|
||||
/* Set maximum distance, see section 9.1. of the spec. */
|
||||
const size_t max_backward_limit = MaxBackwardLimit(params->lgwin);
|
||||
const size_t max_backward_limit = BROTLI_MAX_BACKWARD_LIMIT(params->lgwin);
|
||||
|
||||
const Command* const orig_commands = commands;
|
||||
size_t insert_length = *last_insert_len;
|
||||
@ -32,11 +32,6 @@ static BROTLI_NOINLINE void FN(CreateBackwardReferences)(
|
||||
/* Minimum score to accept a backward reference. */
|
||||
const score_t kMinScore = BROTLI_SCORE_BASE + 400;
|
||||
|
||||
FN(Init)(m, hasher, ringbuffer, params, position, num_bytes, is_last);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
FN(StitchToPreviousBlock)(hasher, num_bytes, position,
|
||||
ringbuffer, ringbuffer_mask);
|
||||
|
||||
while (position + FN(HashTypeLength)() < pos_end) {
|
||||
size_t max_length = pos_end - position;
|
||||
size_t max_distance = BROTLI_MIN(size_t, position, max_backward_limit);
|
||||
|
@ -31,8 +31,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Same as MaxBackwardLimit(18) */
|
||||
#define MAX_DISTANCE ((1 << 18) - BROTLI_WINDOW_GAP)
|
||||
#define MAX_DISTANCE (long)BROTLI_MAX_BACKWARD_LIMIT(18)
|
||||
|
||||
/* kHashMul32 multiplier has these properties:
|
||||
* The multiplier must be odd. Otherwise we may lose the highest bit.
|
||||
|
@ -40,7 +40,7 @@ extern "C" {
|
||||
REQUIRES: All elements in "table[0..table_size-1]" are initialized to zero.
|
||||
REQUIRES: "table_size" is an odd (9, 11, 13, 15) power of two
|
||||
OUTPUT: maximal copy distance <= |input_size|
|
||||
OUTPUT: maximal copy distance <= MaxBackwardLimit(18) */
|
||||
OUTPUT: maximal copy distance <= BROTLI_MAX_BACKWARD_LIMIT(18) */
|
||||
BROTLI_INTERNAL void BrotliCompressFragmentFast(MemoryManager* m,
|
||||
const uint8_t* input,
|
||||
size_t input_size,
|
||||
|
@ -30,8 +30,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Same as MaxBackwardLimit(18) */
|
||||
#define MAX_DISTANCE ((1 << 18) - BROTLI_WINDOW_GAP)
|
||||
#define MAX_DISTANCE (long)BROTLI_MAX_BACKWARD_LIMIT(18)
|
||||
|
||||
/* kHashMul32 multiplier has these properties:
|
||||
* The multiplier must be odd. Otherwise we may lose the highest bit.
|
||||
|
@ -34,7 +34,7 @@ static const size_t kCompressFragmentTwoPassBlockSize = 1 << 17;
|
||||
REQUIRES: All elements in "table[0..table_size-1]" are initialized to zero.
|
||||
REQUIRES: "table_size" is a power of two
|
||||
OUTPUT: maximal copy distance <= |input_size|
|
||||
OUTPUT: maximal copy distance <= MaxBackwardLimit(18) */
|
||||
OUTPUT: maximal copy distance <= BROTLI_MAX_BACKWARD_LIMIT(18) */
|
||||
BROTLI_INTERNAL void BrotliCompressFragmentTwoPass(MemoryManager* m,
|
||||
const uint8_t* input,
|
||||
size_t input_size,
|
||||
|
1120
enc/dictionary_hash.c
Executable file
1120
enc/dictionary_hash.c
Executable file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
65
enc/encode.c
65
enc/encode.c
@ -13,6 +13,7 @@
|
||||
|
||||
#include "../common/version.h"
|
||||
#include "./backward_references.h"
|
||||
#include "./backward_references_hq.h"
|
||||
#include "./bit_cost.h"
|
||||
#include "./brotli_bit_stream.h"
|
||||
#include "./compress_fragment.h"
|
||||
@ -156,6 +157,10 @@ BROTLI_BOOL BrotliEncoderSetParameter(
|
||||
state->params.disable_literal_context_modeling = TO_BROTLI_BOOL(!!value);
|
||||
return BROTLI_TRUE;
|
||||
|
||||
case BROTLI_PARAM_SIZE_HINT:
|
||||
state->params.size_hint = value;
|
||||
return BROTLI_TRUE;
|
||||
|
||||
default: return BROTLI_FALSE;
|
||||
}
|
||||
}
|
||||
@ -583,13 +588,17 @@ static BROTLI_BOOL EnsureInitialized(BrotliEncoderState* s) {
|
||||
return BROTLI_TRUE;
|
||||
}
|
||||
|
||||
static void BrotliEncoderInitState(BrotliEncoderState* s) {
|
||||
s->params.mode = BROTLI_DEFAULT_MODE;
|
||||
s->params.quality = BROTLI_DEFAULT_QUALITY;
|
||||
s->params.lgwin = BROTLI_DEFAULT_WINDOW;
|
||||
s->params.lgblock = 0;
|
||||
s->params.disable_literal_context_modeling = BROTLI_FALSE;
|
||||
static void BrotliEncoderInitParams(BrotliEncoderParams* params) {
|
||||
params->mode = BROTLI_DEFAULT_MODE;
|
||||
params->quality = BROTLI_DEFAULT_QUALITY;
|
||||
params->lgwin = BROTLI_DEFAULT_WINDOW;
|
||||
params->lgblock = 0;
|
||||
params->size_hint = 0;
|
||||
params->disable_literal_context_modeling = BROTLI_FALSE;
|
||||
}
|
||||
|
||||
static void BrotliEncoderInitState(BrotliEncoderState* s) {
|
||||
BrotliEncoderInitParams(&s->params);
|
||||
s->input_pos_ = 0;
|
||||
s->num_commands_ = 0;
|
||||
s->num_literals_ = 0;
|
||||
@ -741,7 +750,7 @@ static void CopyInputToRingBuffer(BrotliEncoderState* s,
|
||||
|
||||
void BrotliEncoderSetCustomDictionary(BrotliEncoderState* s, size_t size,
|
||||
const uint8_t* dict) {
|
||||
size_t max_dict_size = MaxBackwardLimit(s->params.lgwin);
|
||||
size_t max_dict_size = BROTLI_MAX_BACKWARD_LIMIT(s->params.lgwin);
|
||||
size_t dict_size = size;
|
||||
MemoryManager* m = &s->memory_manager_;
|
||||
|
||||
@ -884,17 +893,29 @@ static BROTLI_BOOL EncodeData(
|
||||
}
|
||||
}
|
||||
|
||||
BrotliCreateBackwardReferences(m, bytes, wrapped_last_processed_pos,
|
||||
is_last, data, mask,
|
||||
&s->params,
|
||||
&s->hashers_,
|
||||
s->dist_cache_,
|
||||
&s->last_insert_len_,
|
||||
&s->commands_[s->num_commands_],
|
||||
&s->num_commands_,
|
||||
&s->num_literals_);
|
||||
InitOrStitchToPreviousBlock(m, &s->hashers_, data, mask, &s->params,
|
||||
wrapped_last_processed_pos, bytes, is_last);
|
||||
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
|
||||
|
||||
if (s->params.quality == ZOPFLIFICATION_QUALITY) {
|
||||
BrotliCreateZopfliBackwardReferences(
|
||||
m, bytes, wrapped_last_processed_pos, data, mask,
|
||||
&s->params, s->hashers_.h10, s->dist_cache_, &s->last_insert_len_,
|
||||
&s->commands_[s->num_commands_], &s->num_commands_, &s->num_literals_);
|
||||
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
|
||||
} else if (s->params.quality == HQ_ZOPFLIFICATION_QUALITY) {
|
||||
BrotliCreateHqZopfliBackwardReferences(
|
||||
m, bytes, wrapped_last_processed_pos, data, mask,
|
||||
&s->params, s->hashers_.h10, s->dist_cache_, &s->last_insert_len_,
|
||||
&s->commands_[s->num_commands_], &s->num_commands_, &s->num_literals_);
|
||||
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
|
||||
} else {
|
||||
BrotliCreateBackwardReferences(
|
||||
bytes, wrapped_last_processed_pos, data, mask,
|
||||
&s->params, &s->hashers_, s->dist_cache_, &s->last_insert_len_,
|
||||
&s->commands_[s->num_commands_], &s->num_commands_, &s->num_literals_);
|
||||
}
|
||||
|
||||
{
|
||||
const size_t max_length = MaxMetablockSize(&s->params);
|
||||
const size_t max_literals = max_length / 8;
|
||||
@ -1008,7 +1029,7 @@ static BROTLI_BOOL BrotliCompressBufferQuality10(
|
||||
MemoryManager* m = &memory_manager;
|
||||
|
||||
const size_t mask = BROTLI_SIZE_MAX >> 1;
|
||||
const size_t max_backward_limit = MaxBackwardLimit(lgwin);
|
||||
const size_t max_backward_limit = BROTLI_MAX_BACKWARD_LIMIT(lgwin);
|
||||
int dist_cache[4] = { 4, 11, 15, 16 };
|
||||
int saved_dist_cache[4] = { 4, 11, 15, 16 };
|
||||
BROTLI_BOOL ok = BROTLI_TRUE;
|
||||
@ -1032,11 +1053,9 @@ static BROTLI_BOOL BrotliCompressBufferQuality10(
|
||||
uint8_t prev_byte = 0;
|
||||
uint8_t prev_byte2 = 0;
|
||||
|
||||
params.mode = BROTLI_DEFAULT_MODE;
|
||||
BrotliEncoderInitParams(¶ms);
|
||||
params.quality = 10;
|
||||
params.lgwin = lgwin;
|
||||
params.lgblock = 0;
|
||||
params.disable_literal_context_modeling = BROTLI_FALSE;
|
||||
SanitizeParams(¶ms);
|
||||
params.lgblock = ComputeLgBlock(¶ms);
|
||||
max_block_size = (size_t)1 << params.lgblock;
|
||||
@ -1308,6 +1327,7 @@ BROTLI_BOOL BrotliEncoderCompress(
|
||||
BrotliEncoderSetParameter(s, BROTLI_PARAM_QUALITY, (uint32_t)quality);
|
||||
BrotliEncoderSetParameter(s, BROTLI_PARAM_LGWIN, (uint32_t)lgwin);
|
||||
BrotliEncoderSetParameter(s, BROTLI_PARAM_MODE, (uint32_t)mode);
|
||||
BrotliEncoderSetParameter(s, BROTLI_PARAM_SIZE_HINT, (uint32_t)input_size);
|
||||
result = BrotliEncoderCompressStream(s, BROTLI_OPERATION_FINISH,
|
||||
&available_in, &next_in, &available_out, &next_out, &total_out);
|
||||
if (!BrotliEncoderIsFinished(s)) result = 0;
|
||||
@ -1572,6 +1592,11 @@ BROTLI_BOOL BrotliEncoderCompressStream(
|
||||
BrotliEncoderState* s, BrotliEncoderOperation op, size_t* available_in,
|
||||
const uint8_t** next_in, size_t* available_out,uint8_t** next_out,
|
||||
size_t* total_out) {
|
||||
/* If we don't have any size hint, set it based on the size of the first
|
||||
input chunk. */
|
||||
if (s->params.size_hint == 0) {
|
||||
s->params.size_hint = (uint32_t)*available_in;
|
||||
}
|
||||
if (!EnsureInitialized(s)) return BROTLI_FALSE;
|
||||
|
||||
/* Unfinished metadata block; check requirements. */
|
||||
|
@ -19,7 +19,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
static BROTLI_INLINE uint32_t Log2FloorNonZero(size_t n) {
|
||||
#ifdef __GNUC__
|
||||
#if BROTLI_MODERN_COMPILER || __has_builtin(__builtin_clz)
|
||||
return 31u ^ (uint32_t)__builtin_clz((uint32_t)n);
|
||||
#else
|
||||
uint32_t result = 0;
|
||||
|
368
enc/hash.h
368
enc/hash.h
@ -27,8 +27,6 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MAX_TREE_SEARCH_DEPTH 64
|
||||
#define MAX_TREE_COMP_LENGTH 128
|
||||
#define score_t size_t
|
||||
|
||||
static const uint32_t kDistanceCacheIndex[] = {
|
||||
@ -62,6 +60,7 @@ typedef struct DictionarySearchStatictics {
|
||||
for this use.
|
||||
* The number has been tuned heuristically against compression benchmarks. */
|
||||
static const uint32_t kHashMul32 = 0x1e35a7bd;
|
||||
static const uint64_t kHashMul64 = BROTLI_MAKE_UINT64_T(0x1e35a7bd, 0x1e35a7bd);
|
||||
|
||||
static BROTLI_INLINE uint32_t Hash14(const uint8_t* data) {
|
||||
uint32_t h = BROTLI_UNALIGNED_LOAD32(data) * kHashMul32;
|
||||
@ -221,323 +220,17 @@ static BROTLI_INLINE size_t BackwardMatchLengthCode(const BackwardMatch* self) {
|
||||
#define CAT(a, b) a ## b
|
||||
#define FN(X) EXPAND_CAT(X, HASHER())
|
||||
|
||||
#define MAX_NUM_MATCHES_H10 (64 + MAX_TREE_SEARCH_DEPTH)
|
||||
|
||||
#define HASHER() H10
|
||||
#define HashToBinaryTree HASHER()
|
||||
|
||||
#define BUCKET_BITS 17
|
||||
#define BUCKET_SIZE (1 << BUCKET_BITS)
|
||||
|
||||
static size_t FN(HashTypeLength)(void) { return 4; }
|
||||
static size_t FN(StoreLookahead)(void) { return MAX_TREE_COMP_LENGTH; }
|
||||
|
||||
static uint32_t FN(HashBytes)(const uint8_t *data) {
|
||||
uint32_t h = BROTLI_UNALIGNED_LOAD32(data) * kHashMul32;
|
||||
/* The higher bits contain more mixture from the multiplication,
|
||||
so we take our results from there. */
|
||||
return h >> (32 - BUCKET_BITS);
|
||||
}
|
||||
|
||||
/* A (forgetful) hash table where each hash bucket contains a binary tree of
|
||||
sequences whose first 4 bytes share the same hash code.
|
||||
Each sequence is MAX_TREE_COMP_LENGTH long and is identified by its starting
|
||||
position in the input data. The binary tree is sorted by the lexicographic
|
||||
order of the sequences, and it is also a max-heap with respect to the
|
||||
starting positions. */
|
||||
typedef struct HashToBinaryTree {
|
||||
/* The window size minus 1 */
|
||||
size_t window_mask_;
|
||||
|
||||
/* Hash table that maps the 4-byte hashes of the sequence to the last
|
||||
position where this hash was found, which is the root of the binary
|
||||
tree of sequences that share this hash bucket. */
|
||||
uint32_t buckets_[BUCKET_SIZE];
|
||||
|
||||
/* The union of the binary trees of each hash bucket. The root of the tree
|
||||
corresponding to a hash is a sequence starting at buckets_[hash] and
|
||||
the left and right children of a sequence starting at pos are
|
||||
forest_[2 * pos] and forest_[2 * pos + 1]. */
|
||||
uint32_t* forest_;
|
||||
|
||||
/* A position used to mark a non-existent sequence, i.e. a tree is empty if
|
||||
its root is at invalid_pos_ and a node is a leaf if both its children
|
||||
are at invalid_pos_. */
|
||||
uint32_t invalid_pos_;
|
||||
|
||||
size_t forest_size_;
|
||||
BROTLI_BOOL is_dirty_;
|
||||
} HashToBinaryTree;
|
||||
|
||||
static void FN(Reset)(HashToBinaryTree* self) {
|
||||
self->is_dirty_ = BROTLI_TRUE;
|
||||
}
|
||||
|
||||
static void FN(Initialize)(HashToBinaryTree* self) {
|
||||
self->forest_ = NULL;
|
||||
self->forest_size_ = 0;
|
||||
FN(Reset)(self);
|
||||
}
|
||||
|
||||
static void FN(Cleanup)(MemoryManager* m, HashToBinaryTree* self) {
|
||||
BROTLI_FREE(m, self->forest_);
|
||||
}
|
||||
|
||||
static void FN(Init)(
|
||||
MemoryManager* m, HashToBinaryTree* self, const uint8_t* data,
|
||||
const BrotliEncoderParams* params, size_t position, size_t bytes,
|
||||
BROTLI_BOOL is_last) {
|
||||
if (self->is_dirty_) {
|
||||
uint32_t invalid_pos;
|
||||
size_t num_nodes;
|
||||
uint32_t i;
|
||||
BROTLI_UNUSED(data);
|
||||
self->window_mask_ = (1u << params->lgwin) - 1u;
|
||||
invalid_pos = (uint32_t)(0 - self->window_mask_);
|
||||
self->invalid_pos_ = invalid_pos;
|
||||
for (i = 0; i < BUCKET_SIZE; i++) {
|
||||
self->buckets_[i] = invalid_pos;
|
||||
}
|
||||
num_nodes = (position == 0 && is_last) ? bytes : self->window_mask_ + 1;
|
||||
if (num_nodes > self->forest_size_) {
|
||||
BROTLI_FREE(m, self->forest_);
|
||||
self->forest_ = BROTLI_ALLOC(m, uint32_t, 2 * num_nodes);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
self->forest_size_ = num_nodes;
|
||||
}
|
||||
self->is_dirty_ = BROTLI_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static BROTLI_INLINE size_t FN(LeftChildIndex)(HashToBinaryTree* self,
|
||||
const size_t pos) {
|
||||
return 2 * (pos & self->window_mask_);
|
||||
}
|
||||
|
||||
static BROTLI_INLINE size_t FN(RightChildIndex)(HashToBinaryTree* self,
|
||||
const size_t pos) {
|
||||
return 2 * (pos & self->window_mask_) + 1;
|
||||
}
|
||||
|
||||
/* Stores the hash of the next 4 bytes and in a single tree-traversal, the
|
||||
hash bucket's binary tree is searched for matches and is re-rooted at the
|
||||
current position.
|
||||
|
||||
If less than MAX_TREE_COMP_LENGTH data is available, the hash bucket of the
|
||||
current position is searched for matches, but the state of the hash table
|
||||
is not changed, since we can not know the final sorting order of the
|
||||
current (incomplete) sequence.
|
||||
|
||||
This function must be called with increasing cur_ix positions. */
|
||||
static BROTLI_INLINE BackwardMatch* FN(StoreAndFindMatches)(
|
||||
HashToBinaryTree* self, const uint8_t* const BROTLI_RESTRICT data,
|
||||
const size_t cur_ix, const size_t ring_buffer_mask, const size_t max_length,
|
||||
const size_t max_backward, size_t* const BROTLI_RESTRICT best_len,
|
||||
BackwardMatch* BROTLI_RESTRICT matches) {
|
||||
const size_t cur_ix_masked = cur_ix & ring_buffer_mask;
|
||||
const size_t max_comp_len =
|
||||
BROTLI_MIN(size_t, max_length, MAX_TREE_COMP_LENGTH);
|
||||
const BROTLI_BOOL should_reroot_tree =
|
||||
TO_BROTLI_BOOL(max_length >= MAX_TREE_COMP_LENGTH);
|
||||
const uint32_t key = FN(HashBytes)(&data[cur_ix_masked]);
|
||||
size_t prev_ix = self->buckets_[key];
|
||||
/* The forest index of the rightmost node of the left subtree of the new
|
||||
root, updated as we traverse and re-root the tree of the hash bucket. */
|
||||
size_t node_left = FN(LeftChildIndex)(self, cur_ix);
|
||||
/* The forest index of the leftmost node of the right subtree of the new
|
||||
root, updated as we traverse and re-root the tree of the hash bucket. */
|
||||
size_t node_right = FN(RightChildIndex)(self, cur_ix);
|
||||
/* The match length of the rightmost node of the left subtree of the new
|
||||
root, updated as we traverse and re-root the tree of the hash bucket. */
|
||||
size_t best_len_left = 0;
|
||||
/* The match length of the leftmost node of the right subtree of the new
|
||||
root, updated as we traverse and re-root the tree of the hash bucket. */
|
||||
size_t best_len_right = 0;
|
||||
size_t depth_remaining;
|
||||
if (should_reroot_tree) {
|
||||
self->buckets_[key] = (uint32_t)cur_ix;
|
||||
}
|
||||
for (depth_remaining = MAX_TREE_SEARCH_DEPTH; ; --depth_remaining) {
|
||||
const size_t backward = cur_ix - prev_ix;
|
||||
const size_t prev_ix_masked = prev_ix & ring_buffer_mask;
|
||||
if (backward == 0 || backward > max_backward || depth_remaining == 0) {
|
||||
if (should_reroot_tree) {
|
||||
self->forest_[node_left] = self->invalid_pos_;
|
||||
self->forest_[node_right] = self->invalid_pos_;
|
||||
}
|
||||
break;
|
||||
}
|
||||
{
|
||||
const size_t cur_len = BROTLI_MIN(size_t, best_len_left, best_len_right);
|
||||
size_t len;
|
||||
assert(cur_len <= MAX_TREE_COMP_LENGTH);
|
||||
len = cur_len +
|
||||
FindMatchLengthWithLimit(&data[cur_ix_masked + cur_len],
|
||||
&data[prev_ix_masked + cur_len],
|
||||
max_length - cur_len);
|
||||
assert(0 == memcmp(&data[cur_ix_masked], &data[prev_ix_masked], len));
|
||||
if (matches && len > *best_len) {
|
||||
*best_len = len;
|
||||
InitBackwardMatch(matches++, backward, len);
|
||||
}
|
||||
if (len >= max_comp_len) {
|
||||
if (should_reroot_tree) {
|
||||
self->forest_[node_left] =
|
||||
self->forest_[FN(LeftChildIndex)(self, prev_ix)];
|
||||
self->forest_[node_right] =
|
||||
self->forest_[FN(RightChildIndex)(self, prev_ix)];
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (data[cur_ix_masked + len] > data[prev_ix_masked + len]) {
|
||||
best_len_left = len;
|
||||
if (should_reroot_tree) {
|
||||
self->forest_[node_left] = (uint32_t)prev_ix;
|
||||
}
|
||||
node_left = FN(RightChildIndex)(self, prev_ix);
|
||||
prev_ix = self->forest_[node_left];
|
||||
} else {
|
||||
best_len_right = len;
|
||||
if (should_reroot_tree) {
|
||||
self->forest_[node_right] = (uint32_t)prev_ix;
|
||||
}
|
||||
node_right = FN(LeftChildIndex)(self, prev_ix);
|
||||
prev_ix = self->forest_[node_right];
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
/* Finds all backward matches of &data[cur_ix & ring_buffer_mask] up to the
|
||||
length of max_length and stores the position cur_ix in the hash table.
|
||||
|
||||
Sets *num_matches to the number of matches found, and stores the found
|
||||
matches in matches[0] to matches[*num_matches - 1]. The matches will be
|
||||
sorted by strictly increasing length and (non-strictly) increasing
|
||||
distance. */
|
||||
static BROTLI_INLINE size_t FN(FindAllMatches)(HashToBinaryTree* self,
|
||||
const uint8_t* data, const size_t ring_buffer_mask, const size_t cur_ix,
|
||||
const size_t max_length, const size_t max_backward,
|
||||
const BrotliEncoderParams* params, BackwardMatch* matches) {
|
||||
BackwardMatch* const orig_matches = matches;
|
||||
const size_t cur_ix_masked = cur_ix & ring_buffer_mask;
|
||||
size_t best_len = 1;
|
||||
const size_t short_match_max_backward =
|
||||
params->quality != HQ_ZOPFLIFICATION_QUALITY ? 16 : 64;
|
||||
size_t stop = cur_ix - short_match_max_backward;
|
||||
uint32_t dict_matches[BROTLI_MAX_STATIC_DICTIONARY_MATCH_LEN + 1];
|
||||
size_t i;
|
||||
if (cur_ix < short_match_max_backward) { stop = 0; }
|
||||
for (i = cur_ix - 1; i > stop && best_len <= 2; --i) {
|
||||
size_t prev_ix = i;
|
||||
const size_t backward = cur_ix - prev_ix;
|
||||
if (BROTLI_PREDICT_FALSE(backward > max_backward)) {
|
||||
break;
|
||||
}
|
||||
prev_ix &= ring_buffer_mask;
|
||||
if (data[cur_ix_masked] != data[prev_ix] ||
|
||||
data[cur_ix_masked + 1] != data[prev_ix + 1]) {
|
||||
continue;
|
||||
}
|
||||
{
|
||||
const size_t len =
|
||||
FindMatchLengthWithLimit(&data[prev_ix], &data[cur_ix_masked],
|
||||
max_length);
|
||||
if (len > best_len) {
|
||||
best_len = len;
|
||||
InitBackwardMatch(matches++, backward, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (best_len < max_length) {
|
||||
matches = FN(StoreAndFindMatches)(self, data, cur_ix, ring_buffer_mask,
|
||||
max_length, max_backward, &best_len, matches);
|
||||
}
|
||||
for (i = 0; i <= BROTLI_MAX_STATIC_DICTIONARY_MATCH_LEN; ++i) {
|
||||
dict_matches[i] = kInvalidMatch;
|
||||
}
|
||||
{
|
||||
size_t minlen = BROTLI_MAX(size_t, 4, best_len + 1);
|
||||
if (BrotliFindAllStaticDictionaryMatches(&data[cur_ix_masked], minlen,
|
||||
max_length, &dict_matches[0])) {
|
||||
size_t maxlen = BROTLI_MIN(
|
||||
size_t, BROTLI_MAX_STATIC_DICTIONARY_MATCH_LEN, max_length);
|
||||
size_t l;
|
||||
for (l = minlen; l <= maxlen; ++l) {
|
||||
uint32_t dict_id = dict_matches[l];
|
||||
if (dict_id < kInvalidMatch) {
|
||||
InitDictionaryBackwardMatch(matches++,
|
||||
max_backward + (dict_id >> 5) + 1, l, dict_id & 31);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (size_t)(matches - orig_matches);
|
||||
}
|
||||
|
||||
/* Stores the hash of the next 4 bytes and re-roots the binary tree at the
|
||||
current sequence, without returning any matches.
|
||||
REQUIRES: ix + MAX_TREE_COMP_LENGTH <= end-of-current-block */
|
||||
static BROTLI_INLINE void FN(Store)(HashToBinaryTree* self, const uint8_t *data,
|
||||
const size_t mask, const size_t ix) {
|
||||
/* Maximum distance is window size - 16, see section 9.1. of the spec. */
|
||||
const size_t max_backward = self->window_mask_ - BROTLI_WINDOW_GAP + 1;
|
||||
FN(StoreAndFindMatches)(self, data, ix, mask, MAX_TREE_COMP_LENGTH,
|
||||
max_backward, NULL, NULL);
|
||||
}
|
||||
|
||||
static BROTLI_INLINE void FN(StoreRange)(HashToBinaryTree* self,
|
||||
const uint8_t *data, const size_t mask, const size_t ix_start,
|
||||
const size_t ix_end) {
|
||||
size_t i = ix_start;
|
||||
size_t j = ix_start;
|
||||
if (ix_start + 63 <= ix_end) {
|
||||
i = ix_end - 63;
|
||||
}
|
||||
if (ix_start + 512 <= i) {
|
||||
for (; j < i; j += 8) {
|
||||
FN(Store)(self, data, mask, j);
|
||||
}
|
||||
}
|
||||
for (; i < ix_end; ++i) {
|
||||
FN(Store)(self, data, mask, i);
|
||||
}
|
||||
}
|
||||
|
||||
static BROTLI_INLINE void FN(StitchToPreviousBlock)(HashToBinaryTree* self,
|
||||
size_t num_bytes, size_t position, const uint8_t* ringbuffer,
|
||||
size_t ringbuffer_mask) {
|
||||
if (num_bytes >= FN(HashTypeLength)() - 1 &&
|
||||
position >= MAX_TREE_COMP_LENGTH) {
|
||||
/* Store the last `MAX_TREE_COMP_LENGTH - 1` positions in the hasher.
|
||||
These could not be calculated before, since they require knowledge
|
||||
of both the previous and the current block. */
|
||||
const size_t i_start = position - MAX_TREE_COMP_LENGTH + 1;
|
||||
const size_t i_end = BROTLI_MIN(size_t, position, i_start + num_bytes);
|
||||
size_t i;
|
||||
for (i = i_start; i < i_end; ++i) {
|
||||
/* Maximum distance is window size - 16, see section 9.1. of the spec.
|
||||
Furthermore, we have to make sure that we don't look further back
|
||||
from the start of the next block than the window size, otherwise we
|
||||
could access already overwritten areas of the ring-buffer. */
|
||||
const size_t max_backward =
|
||||
self->window_mask_ - BROTLI_MAX(size_t,
|
||||
BROTLI_WINDOW_GAP - 1,
|
||||
position - i);
|
||||
/* We know that i + MAX_TREE_COMP_LENGTH <= position + num_bytes, i.e. the
|
||||
end of the current block and that we have at least
|
||||
MAX_TREE_COMP_LENGTH tail in the ring-buffer. */
|
||||
FN(StoreAndFindMatches)(self, ringbuffer, i, ringbuffer_mask,
|
||||
MAX_TREE_COMP_LENGTH, max_backward, NULL, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef BUCKET_SIZE
|
||||
#define MAX_TREE_SEARCH_DEPTH 64
|
||||
#define MAX_TREE_COMP_LENGTH 128
|
||||
#include "./hash_to_binary_tree_inc.h" /* NOLINT(build/include) */
|
||||
#undef MAX_TREE_SEARCH_DEPTH
|
||||
#undef MAX_TREE_COMP_LENGTH
|
||||
#undef BUCKET_BITS
|
||||
|
||||
#undef HASHER
|
||||
/* MAX_NUM_MATCHES == 64 + MAX_TREE_SEARCH_DEPTH */
|
||||
#define MAX_NUM_MATCHES_H10 128
|
||||
|
||||
/* For BUCKET_SWEEP == 1, enabling the dictionary lookup makes compression
|
||||
a little faster (0.5% - 1%) and it compresses 0.15% better on small text
|
||||
@ -546,6 +239,7 @@ static BROTLI_INLINE void FN(StitchToPreviousBlock)(HashToBinaryTree* self,
|
||||
#define HASHER() H2
|
||||
#define BUCKET_BITS 16
|
||||
#define BUCKET_SWEEP 1
|
||||
#define HASH_LEN 5
|
||||
#define USE_DICTIONARY 1
|
||||
#include "./hash_longest_match_quickly_inc.h" /* NOLINT(build/include) */
|
||||
#undef BUCKET_SWEEP
|
||||
@ -567,6 +261,7 @@ static BROTLI_INLINE void FN(StitchToPreviousBlock)(HashToBinaryTree* self,
|
||||
#define USE_DICTIONARY 1
|
||||
#include "./hash_longest_match_quickly_inc.h" /* NOLINT(build/include) */
|
||||
#undef USE_DICTIONARY
|
||||
#undef HASH_LEN
|
||||
#undef BUCKET_SWEEP
|
||||
#undef BUCKET_BITS
|
||||
#undef HASHER
|
||||
@ -641,12 +336,24 @@ static BROTLI_INLINE void FN(StitchToPreviousBlock)(HashToBinaryTree* self,
|
||||
|
||||
#undef BUCKET_BITS
|
||||
|
||||
#define HASHER() H54
|
||||
#define BUCKET_BITS 20
|
||||
#define BUCKET_SWEEP 4
|
||||
#define HASH_LEN 7
|
||||
#define USE_DICTIONARY 0
|
||||
#include "./hash_longest_match_quickly_inc.h" /* NOLINT(build/include) */
|
||||
#undef USE_DICTIONARY
|
||||
#undef HASH_LEN
|
||||
#undef BUCKET_SWEEP
|
||||
#undef BUCKET_BITS
|
||||
#undef HASHER
|
||||
|
||||
#undef FN
|
||||
#undef CAT
|
||||
#undef EXPAND_CAT
|
||||
|
||||
#define FOR_GENERIC_HASHERS(H) H(2) H(3) H(4) H(5) H(6) H(7) H(8) H(9) \
|
||||
H(40) H(41) H(42)
|
||||
H(40) H(41) H(42) H(54)
|
||||
#define FOR_ALL_HASHERS(H) FOR_GENERIC_HASHERS(H) H(10)
|
||||
|
||||
typedef struct Hashers {
|
||||
@ -662,8 +369,8 @@ static BROTLI_INLINE void InitHashers(Hashers* self) {
|
||||
}
|
||||
|
||||
static BROTLI_INLINE void DestroyHashers(MemoryManager* m, Hashers* self) {
|
||||
if (self->h10) CleanupH10(m, self->h10);
|
||||
#define CLEANUP_(N) BROTLI_FREE(m, self->h ## N)
|
||||
#define CLEANUP_(N) if (self->h ## N) CleanupH ## N(m, self->h ## N); \
|
||||
BROTLI_FREE(m, self->h ## N);
|
||||
FOR_ALL_HASHERS(CLEANUP_)
|
||||
#undef CLEANUP_
|
||||
}
|
||||
@ -686,7 +393,12 @@ static BROTLI_INLINE void HashersSetup(
|
||||
default: break;
|
||||
}
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
if (type == 10) InitializeH10(self->h10);
|
||||
switch (type) {
|
||||
#define INITIALIZE_(N) case N: InitializeH ## N(self->h ## N); break;
|
||||
FOR_ALL_HASHERS(INITIALIZE_);
|
||||
#undef INITIALIZE_
|
||||
default: break;
|
||||
}
|
||||
HashersReset(self, type);
|
||||
}
|
||||
|
||||
@ -720,6 +432,24 @@ static BROTLI_INLINE void HashersPrependCustomDictionary(
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
}
|
||||
|
||||
static BROTLI_INLINE void InitOrStitchToPreviousBlock(
|
||||
MemoryManager* m, Hashers* self, const uint8_t* data, size_t mask,
|
||||
const BrotliEncoderParams* params, size_t position,
|
||||
size_t bytes, BROTLI_BOOL is_last) {
|
||||
int hasher_type = ChooseHasher(params);
|
||||
switch (hasher_type) {
|
||||
#define INIT_(N) \
|
||||
case N: \
|
||||
InitH ## N(m, self->h ## N, data, params, position, bytes, is_last); \
|
||||
if (BROTLI_IS_OOM(m)) return; \
|
||||
StitchToPreviousBlockH ## N(self->h ## N, bytes, position, data, mask); \
|
||||
break;
|
||||
FOR_ALL_HASHERS(INIT_)
|
||||
#undef INIT_
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
} /* extern "C" */
|
||||
|
@ -56,6 +56,15 @@ typedef struct HashForgetfulChain {
|
||||
size_t max_hops;
|
||||
} HashForgetfulChain;
|
||||
|
||||
static void FN(Initialize)(HashForgetfulChain* self) {
|
||||
BROTLI_UNUSED(self);
|
||||
}
|
||||
|
||||
static void FN(Cleanup)(MemoryManager* m, HashForgetfulChain* self) {
|
||||
BROTLI_UNUSED(m);
|
||||
BROTLI_UNUSED(self);
|
||||
}
|
||||
|
||||
static void FN(Reset)(HashForgetfulChain* self) {
|
||||
self->is_dirty_ = BROTLI_TRUE;
|
||||
DictionarySearchStaticticsReset(&self->dict_search_stats_);
|
||||
|
@ -55,6 +55,15 @@ typedef struct HashLongestMatch {
|
||||
DictionarySearchStatictics dict_search_stats_;
|
||||
} HashLongestMatch;
|
||||
|
||||
static void FN(Initialize)(HashLongestMatch* self) {
|
||||
BROTLI_UNUSED(self);
|
||||
}
|
||||
|
||||
static void FN(Cleanup)(MemoryManager* m, HashLongestMatch* self) {
|
||||
BROTLI_UNUSED(m);
|
||||
BROTLI_UNUSED(self);
|
||||
}
|
||||
|
||||
static void FN(Reset)(HashLongestMatch* self) {
|
||||
self->is_dirty_ = BROTLI_TRUE;
|
||||
DictionarySearchStaticticsReset(&self->dict_search_stats_);
|
||||
|
@ -5,7 +5,9 @@
|
||||
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/* template parameters: FN, BUCKET_BITS, BUCKET_SWEEP, USE_DICTIONARY */
|
||||
/* template parameters: FN, BUCKET_BITS, BUCKET_SWEEP, HASH_LEN,
|
||||
USE_DICTIONARY
|
||||
*/
|
||||
|
||||
#define HashLongestMatchQuickly HASHER()
|
||||
|
||||
@ -20,9 +22,8 @@ static BROTLI_INLINE size_t FN(StoreLookahead)(void) { return 8; }
|
||||
the address in. The HashLongestMatch and HashLongestMatchQuickly
|
||||
classes have separate, different implementations of hashing. */
|
||||
static uint32_t FN(HashBytes)(const uint8_t *data) {
|
||||
/* Computing a hash based on 5 bytes works much better for
|
||||
qualities 1 and 3, where the next hash value is likely to replace */
|
||||
uint64_t h = (BROTLI_UNALIGNED_LOAD64(data) << 24) * kHashMul32;
|
||||
const uint64_t h = ((BROTLI_UNALIGNED_LOAD64(data) << (64 - 8 * HASH_LEN)) *
|
||||
kHashMul64);
|
||||
/* The higher bits contain more mixture from the multiplication,
|
||||
so we take our results from there. */
|
||||
return (uint32_t)(h >> (64 - BUCKET_BITS));
|
||||
@ -40,6 +41,15 @@ typedef struct HashLongestMatchQuickly {
|
||||
DictionarySearchStatictics dict_search_stats_;
|
||||
} HashLongestMatchQuickly;
|
||||
|
||||
static void FN(Initialize)(HashLongestMatchQuickly* self) {
|
||||
BROTLI_UNUSED(self);
|
||||
}
|
||||
|
||||
static void FN(Cleanup)(MemoryManager* m, HashLongestMatchQuickly* self) {
|
||||
BROTLI_UNUSED(m);
|
||||
BROTLI_UNUSED(self);
|
||||
}
|
||||
|
||||
static void FN(Reset)(HashLongestMatchQuickly* self) {
|
||||
self->is_dirty_ = BROTLI_TRUE;
|
||||
DictionarySearchStaticticsReset(&self->dict_search_stats_);
|
||||
|
323
enc/hash_to_binary_tree_inc.h
Executable file
323
enc/hash_to_binary_tree_inc.h
Executable file
@ -0,0 +1,323 @@
|
||||
/* NOLINT(build/header_guard) */
|
||||
/* Copyright 2016 Google Inc. All Rights Reserved.
|
||||
|
||||
Distributed under MIT license.
|
||||
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/* template parameters: FN, BUCKET_BITS, MAX_TREE_COMP_LENGTH,
|
||||
MAX_TREE_SEARCH_DEPTH */
|
||||
|
||||
/* A (forgetful) hash table where each hash bucket contains a binary tree of
|
||||
sequences whose first 4 bytes share the same hash code.
|
||||
Each sequence is MAX_TREE_COMP_LENGTH long and is identified by its starting
|
||||
position in the input data. The binary tree is sorted by the lexicographic
|
||||
order of the sequences, and it is also a max-heap with respect to the
|
||||
starting positions. */
|
||||
|
||||
#define HashToBinaryTree HASHER()
|
||||
|
||||
#define BUCKET_SIZE (1 << BUCKET_BITS)
|
||||
|
||||
static size_t FN(HashTypeLength)(void) { return 4; }
|
||||
static size_t FN(StoreLookahead)(void) { return MAX_TREE_COMP_LENGTH; }
|
||||
|
||||
static uint32_t FN(HashBytes)(const uint8_t *data) {
|
||||
uint32_t h = BROTLI_UNALIGNED_LOAD32(data) * kHashMul32;
|
||||
/* The higher bits contain more mixture from the multiplication,
|
||||
so we take our results from there. */
|
||||
return h >> (32 - BUCKET_BITS);
|
||||
}
|
||||
|
||||
typedef struct HashToBinaryTree {
|
||||
/* The window size minus 1 */
|
||||
size_t window_mask_;
|
||||
|
||||
/* Hash table that maps the 4-byte hashes of the sequence to the last
|
||||
position where this hash was found, which is the root of the binary
|
||||
tree of sequences that share this hash bucket. */
|
||||
uint32_t buckets_[BUCKET_SIZE];
|
||||
|
||||
/* The union of the binary trees of each hash bucket. The root of the tree
|
||||
corresponding to a hash is a sequence starting at buckets_[hash] and
|
||||
the left and right children of a sequence starting at pos are
|
||||
forest_[2 * pos] and forest_[2 * pos + 1]. */
|
||||
uint32_t* forest_;
|
||||
|
||||
/* A position used to mark a non-existent sequence, i.e. a tree is empty if
|
||||
its root is at invalid_pos_ and a node is a leaf if both its children
|
||||
are at invalid_pos_. */
|
||||
uint32_t invalid_pos_;
|
||||
|
||||
size_t forest_size_;
|
||||
BROTLI_BOOL is_dirty_;
|
||||
} HashToBinaryTree;
|
||||
|
||||
static void FN(Reset)(HashToBinaryTree* self) {
|
||||
self->is_dirty_ = BROTLI_TRUE;
|
||||
}
|
||||
|
||||
static void FN(Initialize)(HashToBinaryTree* self) {
|
||||
self->forest_ = NULL;
|
||||
self->forest_size_ = 0;
|
||||
FN(Reset)(self);
|
||||
}
|
||||
|
||||
static void FN(Cleanup)(MemoryManager* m, HashToBinaryTree* self) {
|
||||
BROTLI_FREE(m, self->forest_);
|
||||
}
|
||||
|
||||
static void FN(Init)(
|
||||
MemoryManager* m, HashToBinaryTree* self, const uint8_t* data,
|
||||
const BrotliEncoderParams* params, size_t position, size_t bytes,
|
||||
BROTLI_BOOL is_last) {
|
||||
if (self->is_dirty_) {
|
||||
uint32_t invalid_pos;
|
||||
size_t num_nodes;
|
||||
uint32_t i;
|
||||
BROTLI_UNUSED(data);
|
||||
self->window_mask_ = (1u << params->lgwin) - 1u;
|
||||
invalid_pos = (uint32_t)(0 - self->window_mask_);
|
||||
self->invalid_pos_ = invalid_pos;
|
||||
for (i = 0; i < BUCKET_SIZE; i++) {
|
||||
self->buckets_[i] = invalid_pos;
|
||||
}
|
||||
num_nodes = (position == 0 && is_last) ? bytes : self->window_mask_ + 1;
|
||||
if (num_nodes > self->forest_size_) {
|
||||
BROTLI_FREE(m, self->forest_);
|
||||
self->forest_ = BROTLI_ALLOC(m, uint32_t, 2 * num_nodes);
|
||||
if (BROTLI_IS_OOM(m)) return;
|
||||
self->forest_size_ = num_nodes;
|
||||
}
|
||||
self->is_dirty_ = BROTLI_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static BROTLI_INLINE size_t FN(LeftChildIndex)(HashToBinaryTree* self,
|
||||
const size_t pos) {
|
||||
return 2 * (pos & self->window_mask_);
|
||||
}
|
||||
|
||||
static BROTLI_INLINE size_t FN(RightChildIndex)(HashToBinaryTree* self,
|
||||
const size_t pos) {
|
||||
return 2 * (pos & self->window_mask_) + 1;
|
||||
}
|
||||
|
||||
/* Stores the hash of the next 4 bytes and in a single tree-traversal, the
|
||||
hash bucket's binary tree is searched for matches and is re-rooted at the
|
||||
current position.
|
||||
|
||||
If less than MAX_TREE_COMP_LENGTH data is available, the hash bucket of the
|
||||
current position is searched for matches, but the state of the hash table
|
||||
is not changed, since we can not know the final sorting order of the
|
||||
current (incomplete) sequence.
|
||||
|
||||
This function must be called with increasing cur_ix positions. */
|
||||
static BROTLI_INLINE BackwardMatch* FN(StoreAndFindMatches)(
|
||||
HashToBinaryTree* self, const uint8_t* const BROTLI_RESTRICT data,
|
||||
const size_t cur_ix, const size_t ring_buffer_mask, const size_t max_length,
|
||||
const size_t max_backward, size_t* const BROTLI_RESTRICT best_len,
|
||||
BackwardMatch* BROTLI_RESTRICT matches) {
|
||||
const size_t cur_ix_masked = cur_ix & ring_buffer_mask;
|
||||
const size_t max_comp_len =
|
||||
BROTLI_MIN(size_t, max_length, MAX_TREE_COMP_LENGTH);
|
||||
const BROTLI_BOOL should_reroot_tree =
|
||||
TO_BROTLI_BOOL(max_length >= MAX_TREE_COMP_LENGTH);
|
||||
const uint32_t key = FN(HashBytes)(&data[cur_ix_masked]);
|
||||
size_t prev_ix = self->buckets_[key];
|
||||
/* The forest index of the rightmost node of the left subtree of the new
|
||||
root, updated as we traverse and re-root the tree of the hash bucket. */
|
||||
size_t node_left = FN(LeftChildIndex)(self, cur_ix);
|
||||
/* The forest index of the leftmost node of the right subtree of the new
|
||||
root, updated as we traverse and re-root the tree of the hash bucket. */
|
||||
size_t node_right = FN(RightChildIndex)(self, cur_ix);
|
||||
/* The match length of the rightmost node of the left subtree of the new
|
||||
root, updated as we traverse and re-root the tree of the hash bucket. */
|
||||
size_t best_len_left = 0;
|
||||
/* The match length of the leftmost node of the right subtree of the new
|
||||
root, updated as we traverse and re-root the tree of the hash bucket. */
|
||||
size_t best_len_right = 0;
|
||||
size_t depth_remaining;
|
||||
if (should_reroot_tree) {
|
||||
self->buckets_[key] = (uint32_t)cur_ix;
|
||||
}
|
||||
for (depth_remaining = MAX_TREE_SEARCH_DEPTH; ; --depth_remaining) {
|
||||
const size_t backward = cur_ix - prev_ix;
|
||||
const size_t prev_ix_masked = prev_ix & ring_buffer_mask;
|
||||
if (backward == 0 || backward > max_backward || depth_remaining == 0) {
|
||||
if (should_reroot_tree) {
|
||||
self->forest_[node_left] = self->invalid_pos_;
|
||||
self->forest_[node_right] = self->invalid_pos_;
|
||||
}
|
||||
break;
|
||||
}
|
||||
{
|
||||
const size_t cur_len = BROTLI_MIN(size_t, best_len_left, best_len_right);
|
||||
size_t len;
|
||||
assert(cur_len <= MAX_TREE_COMP_LENGTH);
|
||||
len = cur_len +
|
||||
FindMatchLengthWithLimit(&data[cur_ix_masked + cur_len],
|
||||
&data[prev_ix_masked + cur_len],
|
||||
max_length - cur_len);
|
||||
assert(0 == memcmp(&data[cur_ix_masked], &data[prev_ix_masked], len));
|
||||
if (matches && len > *best_len) {
|
||||
*best_len = len;
|
||||
InitBackwardMatch(matches++, backward, len);
|
||||
}
|
||||
if (len >= max_comp_len) {
|
||||
if (should_reroot_tree) {
|
||||
self->forest_[node_left] =
|
||||
self->forest_[FN(LeftChildIndex)(self, prev_ix)];
|
||||
self->forest_[node_right] =
|
||||
self->forest_[FN(RightChildIndex)(self, prev_ix)];
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (data[cur_ix_masked + len] > data[prev_ix_masked + len]) {
|
||||
best_len_left = len;
|
||||
if (should_reroot_tree) {
|
||||
self->forest_[node_left] = (uint32_t)prev_ix;
|
||||
}
|
||||
node_left = FN(RightChildIndex)(self, prev_ix);
|
||||
prev_ix = self->forest_[node_left];
|
||||
} else {
|
||||
best_len_right = len;
|
||||
if (should_reroot_tree) {
|
||||
self->forest_[node_right] = (uint32_t)prev_ix;
|
||||
}
|
||||
node_right = FN(LeftChildIndex)(self, prev_ix);
|
||||
prev_ix = self->forest_[node_right];
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
/* Finds all backward matches of &data[cur_ix & ring_buffer_mask] up to the
|
||||
length of max_length and stores the position cur_ix in the hash table.
|
||||
|
||||
Sets *num_matches to the number of matches found, and stores the found
|
||||
matches in matches[0] to matches[*num_matches - 1]. The matches will be
|
||||
sorted by strictly increasing length and (non-strictly) increasing
|
||||
distance. */
|
||||
static BROTLI_INLINE size_t FN(FindAllMatches)(HashToBinaryTree* self,
|
||||
const uint8_t* data, const size_t ring_buffer_mask, const size_t cur_ix,
|
||||
const size_t max_length, const size_t max_backward,
|
||||
const BrotliEncoderParams* params, BackwardMatch* matches) {
|
||||
BackwardMatch* const orig_matches = matches;
|
||||
const size_t cur_ix_masked = cur_ix & ring_buffer_mask;
|
||||
size_t best_len = 1;
|
||||
const size_t short_match_max_backward =
|
||||
params->quality != HQ_ZOPFLIFICATION_QUALITY ? 16 : 64;
|
||||
size_t stop = cur_ix - short_match_max_backward;
|
||||
uint32_t dict_matches[BROTLI_MAX_STATIC_DICTIONARY_MATCH_LEN + 1];
|
||||
size_t i;
|
||||
if (cur_ix < short_match_max_backward) { stop = 0; }
|
||||
for (i = cur_ix - 1; i > stop && best_len <= 2; --i) {
|
||||
size_t prev_ix = i;
|
||||
const size_t backward = cur_ix - prev_ix;
|
||||
if (BROTLI_PREDICT_FALSE(backward > max_backward)) {
|
||||
break;
|
||||
}
|
||||
prev_ix &= ring_buffer_mask;
|
||||
if (data[cur_ix_masked] != data[prev_ix] ||
|
||||
data[cur_ix_masked + 1] != data[prev_ix + 1]) {
|
||||
continue;
|
||||
}
|
||||
{
|
||||
const size_t len =
|
||||
FindMatchLengthWithLimit(&data[prev_ix], &data[cur_ix_masked],
|
||||
max_length);
|
||||
if (len > best_len) {
|
||||
best_len = len;
|
||||
InitBackwardMatch(matches++, backward, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (best_len < max_length) {
|
||||
matches = FN(StoreAndFindMatches)(self, data, cur_ix, ring_buffer_mask,
|
||||
max_length, max_backward, &best_len, matches);
|
||||
}
|
||||
for (i = 0; i <= BROTLI_MAX_STATIC_DICTIONARY_MATCH_LEN; ++i) {
|
||||
dict_matches[i] = kInvalidMatch;
|
||||
}
|
||||
{
|
||||
size_t minlen = BROTLI_MAX(size_t, 4, best_len + 1);
|
||||
if (BrotliFindAllStaticDictionaryMatches(&data[cur_ix_masked], minlen,
|
||||
max_length, &dict_matches[0])) {
|
||||
size_t maxlen = BROTLI_MIN(
|
||||
size_t, BROTLI_MAX_STATIC_DICTIONARY_MATCH_LEN, max_length);
|
||||
size_t l;
|
||||
for (l = minlen; l <= maxlen; ++l) {
|
||||
uint32_t dict_id = dict_matches[l];
|
||||
if (dict_id < kInvalidMatch) {
|
||||
InitDictionaryBackwardMatch(matches++,
|
||||
max_backward + (dict_id >> 5) + 1, l, dict_id & 31);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (size_t)(matches - orig_matches);
|
||||
}
|
||||
|
||||
/* Stores the hash of the next 4 bytes and re-roots the binary tree at the
|
||||
current sequence, without returning any matches.
|
||||
REQUIRES: ix + MAX_TREE_COMP_LENGTH <= end-of-current-block */
|
||||
static BROTLI_INLINE void FN(Store)(HashToBinaryTree* self, const uint8_t *data,
|
||||
const size_t mask, const size_t ix) {
|
||||
/* Maximum distance is window size - 16, see section 9.1. of the spec. */
|
||||
const size_t max_backward = self->window_mask_ - BROTLI_WINDOW_GAP + 1;
|
||||
FN(StoreAndFindMatches)(self, data, ix, mask, MAX_TREE_COMP_LENGTH,
|
||||
max_backward, NULL, NULL);
|
||||
}
|
||||
|
||||
static BROTLI_INLINE void FN(StoreRange)(HashToBinaryTree* self,
|
||||
const uint8_t *data, const size_t mask, const size_t ix_start,
|
||||
const size_t ix_end) {
|
||||
size_t i = ix_start;
|
||||
size_t j = ix_start;
|
||||
if (ix_start + 63 <= ix_end) {
|
||||
i = ix_end - 63;
|
||||
}
|
||||
if (ix_start + 512 <= i) {
|
||||
for (; j < i; j += 8) {
|
||||
FN(Store)(self, data, mask, j);
|
||||
}
|
||||
}
|
||||
for (; i < ix_end; ++i) {
|
||||
FN(Store)(self, data, mask, i);
|
||||
}
|
||||
}
|
||||
|
||||
static BROTLI_INLINE void FN(StitchToPreviousBlock)(HashToBinaryTree* self,
|
||||
size_t num_bytes, size_t position, const uint8_t* ringbuffer,
|
||||
size_t ringbuffer_mask) {
|
||||
if (num_bytes >= FN(HashTypeLength)() - 1 &&
|
||||
position >= MAX_TREE_COMP_LENGTH) {
|
||||
/* Store the last `MAX_TREE_COMP_LENGTH - 1` positions in the hasher.
|
||||
These could not be calculated before, since they require knowledge
|
||||
of both the previous and the current block. */
|
||||
const size_t i_start = position - MAX_TREE_COMP_LENGTH + 1;
|
||||
const size_t i_end = BROTLI_MIN(size_t, position, i_start + num_bytes);
|
||||
size_t i;
|
||||
for (i = i_start; i < i_end; ++i) {
|
||||
/* Maximum distance is window size - 16, see section 9.1. of the spec.
|
||||
Furthermore, we have to make sure that we don't look further back
|
||||
from the start of the next block than the window size, otherwise we
|
||||
could access already overwritten areas of the ring-buffer. */
|
||||
const size_t max_backward =
|
||||
self->window_mask_ - BROTLI_MAX(size_t,
|
||||
BROTLI_WINDOW_GAP - 1,
|
||||
position - i);
|
||||
/* We know that i + MAX_TREE_COMP_LENGTH <= position + num_bytes, i.e. the
|
||||
end of the current block and that we have at least
|
||||
MAX_TREE_COMP_LENGTH tail in the ring-buffer. */
|
||||
FN(StoreAndFindMatches)(self, ringbuffer, i, ringbuffer_mask,
|
||||
MAX_TREE_COMP_LENGTH, max_backward, NULL, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef BUCKET_SIZE
|
||||
|
||||
#undef HashToBinaryTree
|
@ -39,7 +39,8 @@ BROTLI_INTERNAL void BrotliInitMemoryManager(
|
||||
void* opaque);
|
||||
|
||||
BROTLI_INTERNAL void* BrotliAllocate(MemoryManager* m, size_t n);
|
||||
#define BROTLI_ALLOC(M, T, N) ((T*)BrotliAllocate((M), (N) * sizeof(T)))
|
||||
#define BROTLI_ALLOC(M, T, N) \
|
||||
((N) ? ((T*)BrotliAllocate((M), (N) * sizeof(T))) : NULL)
|
||||
|
||||
BROTLI_INTERNAL void BrotliFree(MemoryManager* m, void* p);
|
||||
#define BROTLI_FREE(M, P) { \
|
||||
|
@ -37,6 +37,7 @@ typedef struct BrotliEncoderParams {
|
||||
int quality;
|
||||
int lgwin;
|
||||
int lgblock;
|
||||
size_t size_hint;
|
||||
BROTLI_BOOL disable_literal_context_modeling;
|
||||
} BrotliEncoderParams;
|
||||
|
||||
@ -124,6 +125,8 @@ static BROTLI_INLINE size_t LiteralSpreeLengthForSparseSearch(
|
||||
static BROTLI_INLINE int ChooseHasher(const BrotliEncoderParams* params) {
|
||||
if (params->quality > 9) {
|
||||
return 10;
|
||||
} else if (params->quality == 4 && params->size_hint >= (1 << 20)) {
|
||||
return 54;
|
||||
} else if (params->quality < 5) {
|
||||
return params->quality;
|
||||
} else if (params->lgwin <= 16) {
|
||||
|
@ -171,7 +171,13 @@ typedef enum BrotliEncoderParameter {
|
||||
*
|
||||
* This flag is a "decoding-speed vs compression ratio" trade-off.
|
||||
*/
|
||||
BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING = 4
|
||||
BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING = 4,
|
||||
/**
|
||||
* Estimated total input size for all ::BrotliEncoderCompressStream calls.
|
||||
*
|
||||
* The default value is 0, which means that the total input size is unknown.
|
||||
*/
|
||||
BROTLI_PARAM_SIZE_HINT = 5
|
||||
} BrotliEncoderParameter;
|
||||
|
||||
/**
|
||||
|
@ -36,7 +36,7 @@
|
||||
|
||||
#if defined(BROTLI_BUILD_MODERN_COMPILER)
|
||||
#define BROTLI_MODERN_COMPILER 1
|
||||
#elif BROTLI_GCC_VERSION > 300 || BROTLI_ICC_VERSION >= 1600
|
||||
#elif BROTLI_GCC_VERSION >= 304 || BROTLI_ICC_VERSION >= 1600
|
||||
#define BROTLI_MODERN_COMPILER 1
|
||||
#else
|
||||
#define BROTLI_MODERN_COMPILER 0
|
||||
|
Loading…
Reference in New Issue
Block a user