mirror of
https://github.com/google/brotli.git
synced 2024-11-09 21:50:07 +00:00
Update encoder (#504)
* 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
11df843cf0
@ -134,12 +134,14 @@ add_library(brotlidec
|
|||||||
dec/state.c)
|
dec/state.c)
|
||||||
add_library(brotlienc
|
add_library(brotlienc
|
||||||
enc/backward_references.c
|
enc/backward_references.c
|
||||||
|
enc/backward_references_hq.c
|
||||||
enc/bit_cost.c
|
enc/bit_cost.c
|
||||||
enc/block_splitter.c
|
enc/block_splitter.c
|
||||||
enc/brotli_bit_stream.c
|
enc/brotli_bit_stream.c
|
||||||
enc/cluster.c
|
enc/cluster.c
|
||||||
enc/compress_fragment.c
|
enc/compress_fragment.c
|
||||||
enc/compress_fragment_two_pass.c
|
enc/compress_fragment_two_pass.c
|
||||||
|
enc/dictionary_hash.c
|
||||||
enc/encode.c
|
enc/encode.c
|
||||||
enc/entropy_encode.c
|
enc/entropy_encode.c
|
||||||
enc/histogram.c
|
enc/histogram.c
|
||||||
|
@ -50,5 +50,6 @@
|
|||||||
/* Number of slack bytes for window size. Don't confuse
|
/* Number of slack bytes for window size. Don't confuse
|
||||||
with BROTLI_NUM_DISTANCE_SHORT_CODES. */
|
with BROTLI_NUM_DISTANCE_SHORT_CODES. */
|
||||||
#define BROTLI_WINDOW_GAP 16
|
#define BROTLI_WINDOW_GAP 16
|
||||||
|
#define BROTLI_MAX_BACKWARD_LIMIT(W) (((size_t)1 << (W)) - BROTLI_WINDOW_GAP)
|
||||||
|
|
||||||
#endif /* BROTLI_COMMON_CONSTANTS_H_ */
|
#endif /* BROTLI_COMMON_CONSTANTS_H_ */
|
||||||
|
@ -8,205 +8,17 @@
|
|||||||
|
|
||||||
#include "./backward_references.h"
|
#include "./backward_references.h"
|
||||||
|
|
||||||
#include <string.h> /* memcpy, memset */
|
|
||||||
|
|
||||||
#include "../common/constants.h"
|
#include "../common/constants.h"
|
||||||
#include <brotli/types.h>
|
#include <brotli/types.h>
|
||||||
#include "./command.h"
|
#include "./command.h"
|
||||||
#include "./fast_log.h"
|
|
||||||
#include "./find_match_length.h"
|
|
||||||
#include "./literal_cost.h"
|
|
||||||
#include "./memory.h"
|
#include "./memory.h"
|
||||||
#include "./port.h"
|
#include "./port.h"
|
||||||
#include "./prefix.h"
|
|
||||||
#include "./quality.h"
|
#include "./quality.h"
|
||||||
|
|
||||||
#if defined(__cplusplus) || defined(c_plusplus)
|
#if defined(__cplusplus) || defined(c_plusplus)
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#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,
|
static BROTLI_INLINE size_t ComputeDistanceCode(size_t distance,
|
||||||
size_t max_distance,
|
size_t max_distance,
|
||||||
const int* dist_cache) {
|
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;
|
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 EXPAND_CAT(a, b) CAT(a, b)
|
||||||
#define CAT(a, b) a ## b
|
#define CAT(a, b) a ## b
|
||||||
#define FN(X) EXPAND_CAT(X, HASHER())
|
#define FN(X) EXPAND_CAT(X, HASHER())
|
||||||
@ -746,134 +102,17 @@ size_t BrotliZopfliComputeShortestPath(MemoryManager* m,
|
|||||||
#include "./backward_references_inc.h"
|
#include "./backward_references_inc.h"
|
||||||
#undef HASHER
|
#undef HASHER
|
||||||
|
|
||||||
|
#define HASHER() H54
|
||||||
|
/* NOLINTNEXTLINE(build/include) */
|
||||||
|
#include "./backward_references_inc.h"
|
||||||
|
#undef HASHER
|
||||||
|
|
||||||
#undef FN
|
#undef FN
|
||||||
#undef CAT
|
#undef CAT
|
||||||
#undef EXPAND_CAT
|
#undef EXPAND_CAT
|
||||||
|
|
||||||
static BROTLI_NOINLINE void CreateZopfliBackwardReferences(
|
void BrotliCreateBackwardReferences(size_t num_bytes,
|
||||||
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,
|
|
||||||
size_t position,
|
size_t position,
|
||||||
BROTLI_BOOL is_last,
|
|
||||||
const uint8_t* ringbuffer,
|
const uint8_t* ringbuffer,
|
||||||
size_t ringbuffer_mask,
|
size_t ringbuffer_mask,
|
||||||
const BrotliEncoderParams* params,
|
const BrotliEncoderParams* params,
|
||||||
@ -883,24 +122,10 @@ void BrotliCreateBackwardReferences(MemoryManager* m,
|
|||||||
Command* commands,
|
Command* commands,
|
||||||
size_t* num_commands,
|
size_t* num_commands,
|
||||||
size_t* num_literals) {
|
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)) {
|
switch (ChooseHasher(params)) {
|
||||||
#define CASE_(N) \
|
#define CASE_(N) \
|
||||||
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, \
|
ringbuffer, ringbuffer_mask, params, hashers->h ## N, dist_cache, \
|
||||||
last_insert_len, commands, num_commands, num_literals); \
|
last_insert_len, commands, num_commands, num_literals); \
|
||||||
break;
|
break;
|
||||||
@ -909,7 +134,6 @@ void BrotliCreateBackwardReferences(MemoryManager* m,
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (BROTLI_IS_OOM(m)) return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__cplusplus) || defined(c_plusplus)
|
#if defined(__cplusplus) || defined(c_plusplus)
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
#include <brotli/types.h>
|
#include <brotli/types.h>
|
||||||
#include "./command.h"
|
#include "./command.h"
|
||||||
#include "./hash.h"
|
#include "./hash.h"
|
||||||
#include "./memory.h"
|
|
||||||
#include "./port.h"
|
#include "./port.h"
|
||||||
#include "./quality.h"
|
#include "./quality.h"
|
||||||
|
|
||||||
@ -26,73 +25,12 @@ extern "C" {
|
|||||||
CreateBackwardReferences calls, and must be incremented by the amount written
|
CreateBackwardReferences calls, and must be incremented by the amount written
|
||||||
by this call. */
|
by this call. */
|
||||||
BROTLI_INTERNAL void BrotliCreateBackwardReferences(
|
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 uint8_t* ringbuffer, size_t ringbuffer_mask,
|
||||||
const BrotliEncoderParams* params, Hashers* hashers, int* dist_cache,
|
const BrotliEncoderParams* params, Hashers* hashers, int* dist_cache,
|
||||||
size_t* last_insert_len, Command* commands, size_t* num_commands,
|
size_t* last_insert_len, Command* commands, size_t* num_commands,
|
||||||
size_t* num_literals);
|
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)
|
#if defined(__cplusplus) || defined(c_plusplus)
|
||||||
} /* extern "C" */
|
} /* extern "C" */
|
||||||
#endif
|
#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()
|
#define Hasher HASHER()
|
||||||
|
|
||||||
static BROTLI_NOINLINE void FN(CreateBackwardReferences)(
|
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 uint8_t* ringbuffer, size_t ringbuffer_mask,
|
||||||
const BrotliEncoderParams* params, Hasher* hasher, int* dist_cache,
|
const BrotliEncoderParams* params, Hasher* hasher, int* dist_cache,
|
||||||
size_t* last_insert_len, Command* commands, size_t* num_commands,
|
size_t* last_insert_len, Command* commands, size_t* num_commands,
|
||||||
size_t* num_literals) {
|
size_t* num_literals) {
|
||||||
/* Set maximum distance, see section 9.1. of the spec. */
|
/* 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;
|
const Command* const orig_commands = commands;
|
||||||
size_t insert_length = *last_insert_len;
|
size_t insert_length = *last_insert_len;
|
||||||
@ -32,11 +32,6 @@ static BROTLI_NOINLINE void FN(CreateBackwardReferences)(
|
|||||||
/* Minimum score to accept a backward reference. */
|
/* Minimum score to accept a backward reference. */
|
||||||
const score_t kMinScore = BROTLI_SCORE_BASE + 400;
|
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) {
|
while (position + FN(HashTypeLength)() < pos_end) {
|
||||||
size_t max_length = pos_end - position;
|
size_t max_length = pos_end - position;
|
||||||
size_t max_distance = BROTLI_MIN(size_t, position, max_backward_limit);
|
size_t max_distance = BROTLI_MIN(size_t, position, max_backward_limit);
|
||||||
|
@ -31,8 +31,7 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Same as MaxBackwardLimit(18) */
|
#define MAX_DISTANCE (long)BROTLI_MAX_BACKWARD_LIMIT(18)
|
||||||
#define MAX_DISTANCE ((1 << 18) - BROTLI_WINDOW_GAP)
|
|
||||||
|
|
||||||
/* kHashMul32 multiplier has these properties:
|
/* kHashMul32 multiplier has these properties:
|
||||||
* The multiplier must be odd. Otherwise we may lose the highest bit.
|
* 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: 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
|
REQUIRES: "table_size" is an odd (9, 11, 13, 15) power of two
|
||||||
OUTPUT: maximal copy distance <= |input_size|
|
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,
|
BROTLI_INTERNAL void BrotliCompressFragmentFast(MemoryManager* m,
|
||||||
const uint8_t* input,
|
const uint8_t* input,
|
||||||
size_t input_size,
|
size_t input_size,
|
||||||
|
@ -30,8 +30,7 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Same as MaxBackwardLimit(18) */
|
#define MAX_DISTANCE (long)BROTLI_MAX_BACKWARD_LIMIT(18)
|
||||||
#define MAX_DISTANCE ((1 << 18) - BROTLI_WINDOW_GAP)
|
|
||||||
|
|
||||||
/* kHashMul32 multiplier has these properties:
|
/* kHashMul32 multiplier has these properties:
|
||||||
* The multiplier must be odd. Otherwise we may lose the highest bit.
|
* 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: All elements in "table[0..table_size-1]" are initialized to zero.
|
||||||
REQUIRES: "table_size" is a power of two
|
REQUIRES: "table_size" is a power of two
|
||||||
OUTPUT: maximal copy distance <= |input_size|
|
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,
|
BROTLI_INTERNAL void BrotliCompressFragmentTwoPass(MemoryManager* m,
|
||||||
const uint8_t* input,
|
const uint8_t* input,
|
||||||
size_t input_size,
|
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 "../common/version.h"
|
||||||
#include "./backward_references.h"
|
#include "./backward_references.h"
|
||||||
|
#include "./backward_references_hq.h"
|
||||||
#include "./bit_cost.h"
|
#include "./bit_cost.h"
|
||||||
#include "./brotli_bit_stream.h"
|
#include "./brotli_bit_stream.h"
|
||||||
#include "./compress_fragment.h"
|
#include "./compress_fragment.h"
|
||||||
@ -156,6 +157,10 @@ BROTLI_BOOL BrotliEncoderSetParameter(
|
|||||||
state->params.disable_literal_context_modeling = TO_BROTLI_BOOL(!!value);
|
state->params.disable_literal_context_modeling = TO_BROTLI_BOOL(!!value);
|
||||||
return BROTLI_TRUE;
|
return BROTLI_TRUE;
|
||||||
|
|
||||||
|
case BROTLI_PARAM_SIZE_HINT:
|
||||||
|
state->params.size_hint = value;
|
||||||
|
return BROTLI_TRUE;
|
||||||
|
|
||||||
default: return BROTLI_FALSE;
|
default: return BROTLI_FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -583,13 +588,17 @@ static BROTLI_BOOL EnsureInitialized(BrotliEncoderState* s) {
|
|||||||
return BROTLI_TRUE;
|
return BROTLI_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void BrotliEncoderInitState(BrotliEncoderState* s) {
|
static void BrotliEncoderInitParams(BrotliEncoderParams* params) {
|
||||||
s->params.mode = BROTLI_DEFAULT_MODE;
|
params->mode = BROTLI_DEFAULT_MODE;
|
||||||
s->params.quality = BROTLI_DEFAULT_QUALITY;
|
params->quality = BROTLI_DEFAULT_QUALITY;
|
||||||
s->params.lgwin = BROTLI_DEFAULT_WINDOW;
|
params->lgwin = BROTLI_DEFAULT_WINDOW;
|
||||||
s->params.lgblock = 0;
|
params->lgblock = 0;
|
||||||
s->params.disable_literal_context_modeling = BROTLI_FALSE;
|
params->size_hint = 0;
|
||||||
|
params->disable_literal_context_modeling = BROTLI_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BrotliEncoderInitState(BrotliEncoderState* s) {
|
||||||
|
BrotliEncoderInitParams(&s->params);
|
||||||
s->input_pos_ = 0;
|
s->input_pos_ = 0;
|
||||||
s->num_commands_ = 0;
|
s->num_commands_ = 0;
|
||||||
s->num_literals_ = 0;
|
s->num_literals_ = 0;
|
||||||
@ -741,7 +750,7 @@ static void CopyInputToRingBuffer(BrotliEncoderState* s,
|
|||||||
|
|
||||||
void BrotliEncoderSetCustomDictionary(BrotliEncoderState* s, size_t size,
|
void BrotliEncoderSetCustomDictionary(BrotliEncoderState* s, size_t size,
|
||||||
const uint8_t* dict) {
|
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;
|
size_t dict_size = size;
|
||||||
MemoryManager* m = &s->memory_manager_;
|
MemoryManager* m = &s->memory_manager_;
|
||||||
|
|
||||||
@ -884,17 +893,29 @@ static BROTLI_BOOL EncodeData(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BrotliCreateBackwardReferences(m, bytes, wrapped_last_processed_pos,
|
InitOrStitchToPreviousBlock(m, &s->hashers_, data, mask, &s->params,
|
||||||
is_last, data, mask,
|
wrapped_last_processed_pos, bytes, is_last);
|
||||||
&s->params,
|
|
||||||
&s->hashers_,
|
|
||||||
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;
|
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_length = MaxMetablockSize(&s->params);
|
||||||
const size_t max_literals = max_length / 8;
|
const size_t max_literals = max_length / 8;
|
||||||
@ -1008,7 +1029,7 @@ static BROTLI_BOOL BrotliCompressBufferQuality10(
|
|||||||
MemoryManager* m = &memory_manager;
|
MemoryManager* m = &memory_manager;
|
||||||
|
|
||||||
const size_t mask = BROTLI_SIZE_MAX >> 1;
|
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 dist_cache[4] = { 4, 11, 15, 16 };
|
||||||
int saved_dist_cache[4] = { 4, 11, 15, 16 };
|
int saved_dist_cache[4] = { 4, 11, 15, 16 };
|
||||||
BROTLI_BOOL ok = BROTLI_TRUE;
|
BROTLI_BOOL ok = BROTLI_TRUE;
|
||||||
@ -1032,11 +1053,9 @@ static BROTLI_BOOL BrotliCompressBufferQuality10(
|
|||||||
uint8_t prev_byte = 0;
|
uint8_t prev_byte = 0;
|
||||||
uint8_t prev_byte2 = 0;
|
uint8_t prev_byte2 = 0;
|
||||||
|
|
||||||
params.mode = BROTLI_DEFAULT_MODE;
|
BrotliEncoderInitParams(¶ms);
|
||||||
params.quality = 10;
|
params.quality = 10;
|
||||||
params.lgwin = lgwin;
|
params.lgwin = lgwin;
|
||||||
params.lgblock = 0;
|
|
||||||
params.disable_literal_context_modeling = BROTLI_FALSE;
|
|
||||||
SanitizeParams(¶ms);
|
SanitizeParams(¶ms);
|
||||||
params.lgblock = ComputeLgBlock(¶ms);
|
params.lgblock = ComputeLgBlock(¶ms);
|
||||||
max_block_size = (size_t)1 << params.lgblock;
|
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_QUALITY, (uint32_t)quality);
|
||||||
BrotliEncoderSetParameter(s, BROTLI_PARAM_LGWIN, (uint32_t)lgwin);
|
BrotliEncoderSetParameter(s, BROTLI_PARAM_LGWIN, (uint32_t)lgwin);
|
||||||
BrotliEncoderSetParameter(s, BROTLI_PARAM_MODE, (uint32_t)mode);
|
BrotliEncoderSetParameter(s, BROTLI_PARAM_MODE, (uint32_t)mode);
|
||||||
|
BrotliEncoderSetParameter(s, BROTLI_PARAM_SIZE_HINT, (uint32_t)input_size);
|
||||||
result = BrotliEncoderCompressStream(s, BROTLI_OPERATION_FINISH,
|
result = BrotliEncoderCompressStream(s, BROTLI_OPERATION_FINISH,
|
||||||
&available_in, &next_in, &available_out, &next_out, &total_out);
|
&available_in, &next_in, &available_out, &next_out, &total_out);
|
||||||
if (!BrotliEncoderIsFinished(s)) result = 0;
|
if (!BrotliEncoderIsFinished(s)) result = 0;
|
||||||
@ -1572,6 +1592,11 @@ BROTLI_BOOL BrotliEncoderCompressStream(
|
|||||||
BrotliEncoderState* s, BrotliEncoderOperation op, size_t* available_in,
|
BrotliEncoderState* s, BrotliEncoderOperation op, size_t* available_in,
|
||||||
const uint8_t** next_in, size_t* available_out,uint8_t** next_out,
|
const uint8_t** next_in, size_t* available_out,uint8_t** next_out,
|
||||||
size_t* total_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;
|
if (!EnsureInitialized(s)) return BROTLI_FALSE;
|
||||||
|
|
||||||
/* Unfinished metadata block; check requirements. */
|
/* Unfinished metadata block; check requirements. */
|
||||||
|
@ -19,7 +19,7 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static BROTLI_INLINE uint32_t Log2FloorNonZero(size_t n) {
|
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);
|
return 31u ^ (uint32_t)__builtin_clz((uint32_t)n);
|
||||||
#else
|
#else
|
||||||
uint32_t result = 0;
|
uint32_t result = 0;
|
||||||
|
368
enc/hash.h
368
enc/hash.h
@ -27,8 +27,6 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define MAX_TREE_SEARCH_DEPTH 64
|
|
||||||
#define MAX_TREE_COMP_LENGTH 128
|
|
||||||
#define score_t size_t
|
#define score_t size_t
|
||||||
|
|
||||||
static const uint32_t kDistanceCacheIndex[] = {
|
static const uint32_t kDistanceCacheIndex[] = {
|
||||||
@ -62,6 +60,7 @@ typedef struct DictionarySearchStatictics {
|
|||||||
for this use.
|
for this use.
|
||||||
* The number has been tuned heuristically against compression benchmarks. */
|
* The number has been tuned heuristically against compression benchmarks. */
|
||||||
static const uint32_t kHashMul32 = 0x1e35a7bd;
|
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) {
|
static BROTLI_INLINE uint32_t Hash14(const uint8_t* data) {
|
||||||
uint32_t h = BROTLI_UNALIGNED_LOAD32(data) * kHashMul32;
|
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 CAT(a, b) a ## b
|
||||||
#define FN(X) EXPAND_CAT(X, HASHER())
|
#define FN(X) EXPAND_CAT(X, HASHER())
|
||||||
|
|
||||||
#define MAX_NUM_MATCHES_H10 (64 + MAX_TREE_SEARCH_DEPTH)
|
|
||||||
|
|
||||||
#define HASHER() H10
|
#define HASHER() H10
|
||||||
#define HashToBinaryTree HASHER()
|
|
||||||
|
|
||||||
#define BUCKET_BITS 17
|
#define BUCKET_BITS 17
|
||||||
#define BUCKET_SIZE (1 << BUCKET_BITS)
|
#define MAX_TREE_SEARCH_DEPTH 64
|
||||||
|
#define MAX_TREE_COMP_LENGTH 128
|
||||||
static size_t FN(HashTypeLength)(void) { return 4; }
|
#include "./hash_to_binary_tree_inc.h" /* NOLINT(build/include) */
|
||||||
static size_t FN(StoreLookahead)(void) { return MAX_TREE_COMP_LENGTH; }
|
#undef MAX_TREE_SEARCH_DEPTH
|
||||||
|
#undef 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
|
|
||||||
#undef BUCKET_BITS
|
#undef BUCKET_BITS
|
||||||
|
|
||||||
#undef HASHER
|
#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
|
/* 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
|
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 HASHER() H2
|
||||||
#define BUCKET_BITS 16
|
#define BUCKET_BITS 16
|
||||||
#define BUCKET_SWEEP 1
|
#define BUCKET_SWEEP 1
|
||||||
|
#define HASH_LEN 5
|
||||||
#define USE_DICTIONARY 1
|
#define USE_DICTIONARY 1
|
||||||
#include "./hash_longest_match_quickly_inc.h" /* NOLINT(build/include) */
|
#include "./hash_longest_match_quickly_inc.h" /* NOLINT(build/include) */
|
||||||
#undef BUCKET_SWEEP
|
#undef BUCKET_SWEEP
|
||||||
@ -567,6 +261,7 @@ static BROTLI_INLINE void FN(StitchToPreviousBlock)(HashToBinaryTree* self,
|
|||||||
#define USE_DICTIONARY 1
|
#define USE_DICTIONARY 1
|
||||||
#include "./hash_longest_match_quickly_inc.h" /* NOLINT(build/include) */
|
#include "./hash_longest_match_quickly_inc.h" /* NOLINT(build/include) */
|
||||||
#undef USE_DICTIONARY
|
#undef USE_DICTIONARY
|
||||||
|
#undef HASH_LEN
|
||||||
#undef BUCKET_SWEEP
|
#undef BUCKET_SWEEP
|
||||||
#undef BUCKET_BITS
|
#undef BUCKET_BITS
|
||||||
#undef HASHER
|
#undef HASHER
|
||||||
@ -641,12 +336,24 @@ static BROTLI_INLINE void FN(StitchToPreviousBlock)(HashToBinaryTree* self,
|
|||||||
|
|
||||||
#undef BUCKET_BITS
|
#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 FN
|
||||||
#undef CAT
|
#undef CAT
|
||||||
#undef EXPAND_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) \
|
#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)
|
#define FOR_ALL_HASHERS(H) FOR_GENERIC_HASHERS(H) H(10)
|
||||||
|
|
||||||
typedef struct Hashers {
|
typedef struct Hashers {
|
||||||
@ -662,8 +369,8 @@ static BROTLI_INLINE void InitHashers(Hashers* self) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static BROTLI_INLINE void DestroyHashers(MemoryManager* m, Hashers* self) {
|
static BROTLI_INLINE void DestroyHashers(MemoryManager* m, Hashers* self) {
|
||||||
if (self->h10) CleanupH10(m, self->h10);
|
#define CLEANUP_(N) if (self->h ## N) CleanupH ## N(m, self->h ## N); \
|
||||||
#define CLEANUP_(N) BROTLI_FREE(m, self->h ## N)
|
BROTLI_FREE(m, self->h ## N);
|
||||||
FOR_ALL_HASHERS(CLEANUP_)
|
FOR_ALL_HASHERS(CLEANUP_)
|
||||||
#undef CLEANUP_
|
#undef CLEANUP_
|
||||||
}
|
}
|
||||||
@ -686,7 +393,12 @@ static BROTLI_INLINE void HashersSetup(
|
|||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
if (BROTLI_IS_OOM(m)) return;
|
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);
|
HashersReset(self, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -720,6 +432,24 @@ static BROTLI_INLINE void HashersPrependCustomDictionary(
|
|||||||
if (BROTLI_IS_OOM(m)) return;
|
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)
|
#if defined(__cplusplus) || defined(c_plusplus)
|
||||||
} /* extern "C" */
|
} /* extern "C" */
|
||||||
|
@ -56,6 +56,15 @@ typedef struct HashForgetfulChain {
|
|||||||
size_t max_hops;
|
size_t max_hops;
|
||||||
} HashForgetfulChain;
|
} 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) {
|
static void FN(Reset)(HashForgetfulChain* self) {
|
||||||
self->is_dirty_ = BROTLI_TRUE;
|
self->is_dirty_ = BROTLI_TRUE;
|
||||||
DictionarySearchStaticticsReset(&self->dict_search_stats_);
|
DictionarySearchStaticticsReset(&self->dict_search_stats_);
|
||||||
|
@ -55,6 +55,15 @@ typedef struct HashLongestMatch {
|
|||||||
DictionarySearchStatictics dict_search_stats_;
|
DictionarySearchStatictics dict_search_stats_;
|
||||||
} HashLongestMatch;
|
} 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) {
|
static void FN(Reset)(HashLongestMatch* self) {
|
||||||
self->is_dirty_ = BROTLI_TRUE;
|
self->is_dirty_ = BROTLI_TRUE;
|
||||||
DictionarySearchStaticticsReset(&self->dict_search_stats_);
|
DictionarySearchStaticticsReset(&self->dict_search_stats_);
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
|
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()
|
#define HashLongestMatchQuickly HASHER()
|
||||||
|
|
||||||
@ -20,9 +22,8 @@ static BROTLI_INLINE size_t FN(StoreLookahead)(void) { return 8; }
|
|||||||
the address in. The HashLongestMatch and HashLongestMatchQuickly
|
the address in. The HashLongestMatch and HashLongestMatchQuickly
|
||||||
classes have separate, different implementations of hashing. */
|
classes have separate, different implementations of hashing. */
|
||||||
static uint32_t FN(HashBytes)(const uint8_t *data) {
|
static uint32_t FN(HashBytes)(const uint8_t *data) {
|
||||||
/* Computing a hash based on 5 bytes works much better for
|
const uint64_t h = ((BROTLI_UNALIGNED_LOAD64(data) << (64 - 8 * HASH_LEN)) *
|
||||||
qualities 1 and 3, where the next hash value is likely to replace */
|
kHashMul64);
|
||||||
uint64_t h = (BROTLI_UNALIGNED_LOAD64(data) << 24) * kHashMul32;
|
|
||||||
/* The higher bits contain more mixture from the multiplication,
|
/* The higher bits contain more mixture from the multiplication,
|
||||||
so we take our results from there. */
|
so we take our results from there. */
|
||||||
return (uint32_t)(h >> (64 - BUCKET_BITS));
|
return (uint32_t)(h >> (64 - BUCKET_BITS));
|
||||||
@ -40,6 +41,15 @@ typedef struct HashLongestMatchQuickly {
|
|||||||
DictionarySearchStatictics dict_search_stats_;
|
DictionarySearchStatictics dict_search_stats_;
|
||||||
} HashLongestMatchQuickly;
|
} 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) {
|
static void FN(Reset)(HashLongestMatchQuickly* self) {
|
||||||
self->is_dirty_ = BROTLI_TRUE;
|
self->is_dirty_ = BROTLI_TRUE;
|
||||||
DictionarySearchStaticticsReset(&self->dict_search_stats_);
|
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);
|
void* opaque);
|
||||||
|
|
||||||
BROTLI_INTERNAL void* BrotliAllocate(MemoryManager* m, size_t n);
|
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);
|
BROTLI_INTERNAL void BrotliFree(MemoryManager* m, void* p);
|
||||||
#define BROTLI_FREE(M, P) { \
|
#define BROTLI_FREE(M, P) { \
|
||||||
|
@ -37,6 +37,7 @@ typedef struct BrotliEncoderParams {
|
|||||||
int quality;
|
int quality;
|
||||||
int lgwin;
|
int lgwin;
|
||||||
int lgblock;
|
int lgblock;
|
||||||
|
size_t size_hint;
|
||||||
BROTLI_BOOL disable_literal_context_modeling;
|
BROTLI_BOOL disable_literal_context_modeling;
|
||||||
} BrotliEncoderParams;
|
} BrotliEncoderParams;
|
||||||
|
|
||||||
@ -124,6 +125,8 @@ static BROTLI_INLINE size_t LiteralSpreeLengthForSparseSearch(
|
|||||||
static BROTLI_INLINE int ChooseHasher(const BrotliEncoderParams* params) {
|
static BROTLI_INLINE int ChooseHasher(const BrotliEncoderParams* params) {
|
||||||
if (params->quality > 9) {
|
if (params->quality > 9) {
|
||||||
return 10;
|
return 10;
|
||||||
|
} else if (params->quality == 4 && params->size_hint >= (1 << 20)) {
|
||||||
|
return 54;
|
||||||
} else if (params->quality < 5) {
|
} else if (params->quality < 5) {
|
||||||
return params->quality;
|
return params->quality;
|
||||||
} else if (params->lgwin <= 16) {
|
} else if (params->lgwin <= 16) {
|
||||||
|
@ -171,7 +171,13 @@ typedef enum BrotliEncoderParameter {
|
|||||||
*
|
*
|
||||||
* This flag is a "decoding-speed vs compression ratio" trade-off.
|
* 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;
|
} BrotliEncoderParameter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
#if defined(BROTLI_BUILD_MODERN_COMPILER)
|
#if defined(BROTLI_BUILD_MODERN_COMPILER)
|
||||||
#define BROTLI_MODERN_COMPILER 1
|
#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
|
#define BROTLI_MODERN_COMPILER 1
|
||||||
#else
|
#else
|
||||||
#define BROTLI_MODERN_COMPILER 0
|
#define BROTLI_MODERN_COMPILER 0
|
||||||
|
4
setup.py
4
setup.py
@ -170,12 +170,14 @@ EXT_MODULES = [
|
|||||||
'dec/huffman.c',
|
'dec/huffman.c',
|
||||||
'dec/state.c',
|
'dec/state.c',
|
||||||
'enc/backward_references.c',
|
'enc/backward_references.c',
|
||||||
|
'enc/backward_references_hq.c',
|
||||||
'enc/bit_cost.c',
|
'enc/bit_cost.c',
|
||||||
'enc/block_splitter.c',
|
'enc/block_splitter.c',
|
||||||
'enc/brotli_bit_stream.c',
|
'enc/brotli_bit_stream.c',
|
||||||
'enc/cluster.c',
|
'enc/cluster.c',
|
||||||
'enc/compress_fragment.c',
|
'enc/compress_fragment.c',
|
||||||
'enc/compress_fragment_two_pass.c',
|
'enc/compress_fragment_two_pass.c',
|
||||||
|
'enc/dictionary_hash.c',
|
||||||
'enc/encode.c',
|
'enc/encode.c',
|
||||||
'enc/entropy_encode.c',
|
'enc/entropy_encode.c',
|
||||||
'enc/histogram.c',
|
'enc/histogram.c',
|
||||||
@ -199,6 +201,7 @@ EXT_MODULES = [
|
|||||||
'dec/streams.h',
|
'dec/streams.h',
|
||||||
'dec/transform.h',
|
'dec/transform.h',
|
||||||
'enc/backward_references.h',
|
'enc/backward_references.h',
|
||||||
|
'enc/backward_references_hq.h',
|
||||||
'enc/backward_references_inc.h',
|
'enc/backward_references_inc.h',
|
||||||
'enc/bit_cost.h',
|
'enc/bit_cost.h',
|
||||||
'enc/bit_cost_inc.h',
|
'enc/bit_cost_inc.h',
|
||||||
@ -217,6 +220,7 @@ EXT_MODULES = [
|
|||||||
'enc/fast_log.h',
|
'enc/fast_log.h',
|
||||||
'enc/find_match_length.h',
|
'enc/find_match_length.h',
|
||||||
'enc/hash.h',
|
'enc/hash.h',
|
||||||
|
'enc/hash_to_binary_tree_inc.h',
|
||||||
'enc/hash_longest_match_inc.h',
|
'enc/hash_longest_match_inc.h',
|
||||||
'enc/hash_longest_match_quickly_inc.h',
|
'enc/hash_longest_match_quickly_inc.h',
|
||||||
'enc/histogram.h',
|
'enc/histogram.h',
|
||||||
|
Loading…
Reference in New Issue
Block a user