Roll brotli to latest HEAD commit The brotli release from 3 weeks ago (v1.2.1) was from a branch that predates our previous version and is missing a lot of fixes from the last year so this fast-forwards to the current HEAD commit. Bug: 585364170 Change-Id: I8e659128646b294791e0f3c1cfd2c9a812e9a997 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8812621 Reviewed-by: Kenichi Ishibashi <bashi@chromium.org> Commit-Queue: Patrick Meenan <pmeenan@chromium.org> Cr-Commit-Position: refs/heads/main@{#1744715} NOKEYCHECK=True GitOrigin-RevId: 4b6765a3fe2e82458d1811bca527fa147ca4b8cf
diff --git a/BUILD.gn b/BUILD.gn index 02237cc..d57b270 100644 --- a/BUILD.gn +++ b/BUILD.gn
@@ -30,6 +30,7 @@ "include/brotli/decode.h", "include/brotli/encode.h", "include/brotli/port.h", + "include/brotli/shared_dictionary.h", "include/brotli/types.h", ] } @@ -88,6 +89,7 @@ "enc/backward_references_hq.c", "enc/backward_references_hq.h", "enc/backward_references_inc.h", + "enc/backward_references_opt_inc.h", "enc/bit_cost.c", "enc/bit_cost.h", "enc/bit_cost_inc.h", @@ -129,6 +131,7 @@ "enc/hash_longest_match_inc.h", "enc/hash_longest_match_quickly_inc.h", "enc/hash_longest_match_simd_inc.h", + "enc/hash_longest_match_simd_opt_inc.h", "enc/hash_rolling_inc.h", "enc/hash_to_binary_tree_inc.h", "enc/histogram.c",
diff --git a/README.chromium b/README.chromium index bee4db5..00acfaa 100644 --- a/README.chromium +++ b/README.chromium
@@ -1,7 +1,7 @@ Name: Brotli URL: https://github.com/google/brotli -Version: cfa184a997ac19454d5a3ae97f6d600aac2445a6 -Revision: cfa184a997ac19454d5a3ae97f6d600aac2445a6 +Version: d89c4b1c52e25ddafe047bc78bbee5c1e24b1809 +Revision: d89c4b1c52e25ddafe047bc78bbee5c1e24b1809 Update Mechanism: Manual License: MIT License File: LICENSE
diff --git a/common/platform.h b/common/platform.h index e1254d5..d4d4723 100644 --- a/common/platform.h +++ b/common/platform.h
@@ -24,6 +24,7 @@ #ifndef BROTLI_COMMON_PLATFORM_H_ #define BROTLI_COMMON_PLATFORM_H_ +#include <limits.h> #include <string.h> /* IWYU pragma: export memcmp, memcpy, memset */ #include <stdlib.h> /* IWYU pragma: export exit, free, malloc */ #include <sys/types.h> /* should include endian.h for us */ @@ -522,6 +523,14 @@ #define BROTLI_MIN(T, A, B) (brotli_min_ ## T((A), (B))) #define BROTLI_MAX(T, A, B) (brotli_max_ ## T((A), (B))) +static BROTLI_INLINE int brotli_safe_add_int(int a, int b, int* result) { + if (b > 0 && a > INT_MAX - b) return 0; + if (b < 0 && a < INT_MIN - b) return 0; + *result = a + b; + return 1; +} +#define BROTLI_SAFE_ADD(T, A, B, R) (brotli_safe_add_ ## T((A), (B), (R))) + #define BROTLI_SWAP(T, A, I, J) { \ T __brotli_swap_tmp = (A)[(I)]; \ (A)[(I)] = (A)[(J)]; \ @@ -607,6 +616,7 @@ BROTLI_UNUSED(&brotli_max_uint32_t); BROTLI_UNUSED(&brotli_min_uint8_t); BROTLI_UNUSED(&brotli_max_uint8_t); + BROTLI_UNUSED(&brotli_safe_add_int); BROTLI_UNUSED(&BrotliDefaultAllocFunc); BROTLI_UNUSED(&BrotliDefaultFreeFunc); BROTLI_UNUSED(&BrotliRotateRight16); @@ -615,6 +625,7 @@ #if BROTLI_ENABLE_DUMP BROTLI_UNUSED(&BrotliDump); #endif +} #if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_I86)) && \ !defined(_M_ARM64EC) @@ -650,11 +661,12 @@ /* The SIMD matchers are only faster at certain quality levels. */ #if defined(_M_X64) && defined(BROTLI_TZCNT64) +#define BROTLI_MAX_RECOMMENDED_SIMD_QUALITY 7 #define BROTLI_MAX_SIMD_QUALITY 7 #elif defined(BROTLI_TZCNT64) -#define BROTLI_MAX_SIMD_QUALITY 6 +#define BROTLI_MAX_RECOMMENDED_SIMD_QUALITY 6 +#define BROTLI_MAX_SIMD_QUALITY 7 #endif -} #if defined(_MSC_VER) #define BROTLI_CRASH() __debugbreak(), (void)abort()
diff --git a/dec/decode.c b/dec/decode.c index 19c7e02..1ab505e 100644 --- a/dec/decode.c +++ b/dec/decode.c
@@ -11,6 +11,7 @@ #include "../common/dictionary.h" #include "../common/platform.h" #include "../common/shared_dictionary_internal.h" +#include <brotli/shared_dictionary.h> #include "../common/transform.h" #include "../common/version.h" #include "bit_reader.h" @@ -481,6 +482,9 @@ uint8_t* ringbuffer, int pos, const int limit) { + const int kMaximalOverread = 4; + int pos_limit = limit; + int copies = 0; /* Calculate range where CheckInputAmount is always true. Start with the number of bytes we can read. */ int64_t new_lim = br->guard_in - br->next_in; @@ -488,9 +492,6 @@ new_lim *= 8; /* At most 15 bits per symbol, so this is safe. */ new_lim /= 15; - const int kMaximalOverread = 4; - int pos_limit = limit; - int copies = 0; if ((new_lim - kMaximalOverread) <= limit) { // Safe cast, since new_lim is already < num_steps pos_limit = (int)(new_lim - kMaximalOverread); @@ -1317,7 +1318,7 @@ return DecodeDistanceBlockSwitchInternal(0, s); } -static BROTLI_BOOL BROTLI_NOINLINE SafeDecodeDistanceBlockSwitch( +static BROTLI_NOINLINE BrotliDecoderErrorCode SafeDecodeDistanceBlockSwitch( BrotliDecoderState* s) { return DecodeDistanceBlockSwitchInternal(1, s); } @@ -1526,23 +1527,34 @@ static BROTLI_BOOL AttachCompoundDictionary( BrotliDecoderState* state, const uint8_t* data, size_t size) { BrotliDecoderCompoundDictionary* addon = state->compound_dictionary; + /* Soft lie: no dictionary is attached; i.e. this call is not accounted + * towards SHARED_BROTLI_MAX_COMPOUND_DICTS limit. */ + if (size == 0) return BROTLI_TRUE; + if (size > SHARED_BROTLI_MAX_RAW_DICT_SIZE) return BROTLI_FALSE; if (state->state != BROTLI_STATE_UNINITED) return BROTLI_FALSE; if (!addon) { addon = (BrotliDecoderCompoundDictionary*)BROTLI_DECODER_ALLOC( state, sizeof(BrotliDecoderCompoundDictionary)); if (!addon) return BROTLI_FALSE; - addon->num_chunks = 0; - addon->total_size = 0; - addon->br_length = 0; - addon->br_copied = 0; - addon->block_bits = -1; - addon->chunk_offsets[0] = 0; + addon->num_chunks = 0u; + addon->block_bits = 255u; + addon->br_index = 0u; + addon->total_size = 0u; + addon->br_offset = 0u; + addon->br_length = 0u; + addon->br_copied = 0u; + addon->chunk_offsets[0] = 0u; state->compound_dictionary = addon; } - if (addon->num_chunks == 15) return BROTLI_FALSE; + if (addon->num_chunks == SHARED_BROTLI_MAX_COMPOUND_DICTS) { + return BROTLI_FALSE; + } + if (size > SHARED_BROTLI_MAX_RAW_DICT_SIZE - addon->total_size) { + return BROTLI_FALSE; + } addon->chunks[addon->num_chunks] = data; addon->num_chunks++; - addon->total_size += (int)size; + addon->total_size += (uint32_t)size; addon->chunk_offsets[addon->num_chunks] = addon->total_size; return BROTLI_TRUE; } @@ -1550,41 +1562,51 @@ static void EnsureCompoundDictionaryInitialized(BrotliDecoderState* state) { BrotliDecoderCompoundDictionary* addon = state->compound_dictionary; /* 256 = (1 << 8) slots in block map. */ - int block_bits = 8; - int cursor = 0; - int index = 0; - if (addon->block_bits != -1) return; - while (((addon->total_size - 1) >> block_bits) != 0) block_bits++; - block_bits -= 8; - addon->block_bits = block_bits; - while (cursor < addon->total_size) { - while (addon->chunk_offsets[index + 1] < cursor) index++; + size_t block_bits = 8u; + uint32_t cursor = 0u; + size_t index = 0u; + uint32_t maximal_address = addon->total_size - 1u; + BROTLI_DCHECK(addon->total_size > 0u); + if (addon->block_bits != 255u) return; + while ((maximal_address >> block_bits) != 0u) block_bits++; + block_bits -= 8u; + addon->block_bits = (uint8_t)block_bits; + while (cursor <= maximal_address) { + /* We have sentinel value equal maximal_address + 1. */ + while (addon->chunk_offsets[index + 1u] < cursor) index++; addon->block_map[cursor >> block_bits] = (uint8_t)index; - cursor += 1 << block_bits; + cursor += 1u << block_bits; } + /* Now if X is in the range [0..maximal_address] then + * block_map[X >> block_bits] is in [0..num_chunks). */ } static BROTLI_BOOL InitializeCompoundDictionaryCopy(BrotliDecoderState* s, - int address, int length) { + uint32_t address, uint32_t length) { BrotliDecoderCompoundDictionary* addon = s->compound_dictionary; - int index; + size_t index; + BROTLI_DCHECK(addon->total_size > 0u); + BROTLI_DCHECK(address < addon->total_size); + BROTLI_DCHECK(length > 0u); EnsureCompoundDictionaryInitialized(s); index = addon->block_map[address >> addon->block_bits]; + /* Several chunks might be mapped to the same block index. */ while (address >= addon->chunk_offsets[index + 1]) index++; - if (addon->total_size < address + length) return BROTLI_FALSE; + /* Check that the whole chunk is within dictionary bounds. */ + if (length > addon->total_size - address) return BROTLI_FALSE; /* Update the recent distances cache. */ s->dist_rb[s->dist_rb_idx & 3] = s->distance_code; ++s->dist_rb_idx; - s->meta_block_remaining_len -= length; - addon->br_index = index; + s->meta_block_remaining_len -= (int)length; + addon->br_index = (uint16_t)index; addon->br_offset = address - addon->chunk_offsets[index]; addon->br_length = length; - addon->br_copied = 0; + addon->br_copied = 0u; return BROTLI_TRUE; } -static int GetCompoundDictionarySize(BrotliDecoderState* s) { - return s->compound_dictionary ? s->compound_dictionary->total_size : 0; +static uint32_t GetCompoundDictionarySize(BrotliDecoderState* s) { + return s->compound_dictionary ? s->compound_dictionary->total_size : 0u; } static int CopyFromCompoundDictionary(BrotliDecoderState* s, int pos) { @@ -1595,18 +1617,19 @@ const uint8_t* copy_src = addon->chunks[addon->br_index] + addon->br_offset; int space = s->ringbuffer_size - pos; - int rem_chunk_length = (addon->chunk_offsets[addon->br_index + 1] - - addon->chunk_offsets[addon->br_index]) - addon->br_offset; - int length = addon->br_length - addon->br_copied; + uint32_t rem_chunk_length = (addon->chunk_offsets[addon->br_index + 1] - + addon->chunk_offsets[addon->br_index]) - + addon->br_offset; + uint32_t length = addon->br_length - addon->br_copied; if (length > rem_chunk_length) length = rem_chunk_length; - if (length > space) length = space; + if (length > (uint32_t)space) length = (uint32_t)space; memcpy(copy_dst, copy_src, (size_t)length); - pos += length; + pos += (int)length; addon->br_offset += length; addon->br_copied += length; if (length == rem_chunk_length) { addon->br_index++; - addon->br_offset = 0; + addon->br_offset = 0u; } if (pos == s->ringbuffer_size) break; } @@ -1985,7 +2008,7 @@ int i = s->loop_counter; BrotliDecoderErrorCode result = BROTLI_DECODER_SUCCESS; BrotliBitReader* br = &s->br; - int compound_dictionary_size = GetCompoundDictionarySize(s); + uint32_t compound_dictionary_size = GetCompoundDictionarySize(s); if (!CheckInputAmount(safe, br)) { result = BROTLI_DECODER_NEEDS_MORE_INPUT; @@ -2086,10 +2109,10 @@ } while (--i != 0); } else { /* safe */ do { + brotli_reg_t literal; if (BROTLI_PREDICT_FALSE(s->block_length[0] == 0)) { goto NextLiteralBlock; } - brotli_reg_t literal; if (!SafeReadSymbol(s->literal_htree, br, &literal)) { result = BROTLI_DECODER_NEEDS_MORE_INPUT; goto saveStateAndReturn; @@ -2186,10 +2209,14 @@ pos, s->distance_code, i, s->meta_block_remaining_len)); return BROTLI_FAILURE(BROTLI_DECODER_ERROR_FORMAT_DISTANCE); } - if (s->distance_code - s->max_distance - 1 < compound_dictionary_size) { - int address = compound_dictionary_size - - (s->distance_code - s->max_distance); - if (!InitializeCompoundDictionaryCopy(s, address, i)) { + /* Check that LZ77-dictionary address is non-negative. */ + if ((uint32_t)(s->distance_code - s->max_distance) - 1u < + compound_dictionary_size) { + /* Given that `s->distance_code - s->max_distance > 0` we have `address` + * is strictly less than `compound_dictionary_size`. */ + uint32_t address = compound_dictionary_size - + (uint32_t)(s->distance_code - s->max_distance); + if (!InitializeCompoundDictionaryCopy(s, address, (uint32_t)i)) { return BROTLI_FAILURE(BROTLI_DECODER_ERROR_COMPOUND_DICTIONARY); } pos += CopyFromCompoundDictionary(s, pos); @@ -2197,6 +2224,9 @@ s->state = BROTLI_STATE_COMMAND_POST_WRITE_1; goto saveStateAndReturn; } + /* In else branch we have: + * `s->distance_code - s->max_distance - 1 >= compound_dictionary_size`; + * that implies that `compound_dictionary_size` could be cast to int. */ } else if (i >= SHARED_BROTLI_MIN_DICTIONARY_WORD_LENGTH && i <= SHARED_BROTLI_MAX_DICTIONARY_WORD_LENGTH) { uint8_t p1 = s->ringbuffer[(pos - 1) & s->ringbuffer_mask]; @@ -2208,8 +2238,8 @@ const BrotliTransforms* transforms = s->dictionary->transforms[dict_id]; int offset = (int)words->offsets_by_length[i]; brotli_reg_t shift = words->size_bits_by_length[i]; - int address = - s->distance_code - s->max_distance - 1 - compound_dictionary_size; + int address = s->distance_code - s->max_distance - 1 - + (int)compound_dictionary_size; int mask = (int)BitMask(shift); int word_idx = address & mask; int transform_idx = address >> shift;
diff --git a/dec/state.h b/dec/state.h index aa371fb..4fcdb4a 100644 --- a/dec/state.h +++ b/dec/state.h
@@ -190,15 +190,15 @@ /* BrotliDecoderState addon, used for Compound Dictionary functionality. */ typedef struct BrotliDecoderCompoundDictionary { - int num_chunks; - int total_size; - int br_index; - int br_offset; - int br_length; - int br_copied; + uint8_t num_chunks; + uint8_t block_bits; + uint16_t br_index; + uint32_t total_size; + uint32_t br_offset; + uint32_t br_length; + uint32_t br_copied; const uint8_t* chunks[16]; - int chunk_offsets[16]; - int block_bits; + uint32_t chunk_offsets[16]; uint8_t block_map[256]; } BrotliDecoderCompoundDictionary;
diff --git a/enc/backward_references.c b/enc/backward_references.c index b48dd85..1a7d3d0 100644 --- a/enc/backward_references.c +++ b/enc/backward_references.c
@@ -18,6 +18,98 @@ #include "params.h" #include "quality.h" /* IWYU pragma: keep for inc */ +BROTLI_INTERNAL const BROTLI_MODEL("small") + uint8_t kIsBase64[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 1, /* 43 '+', 47 '/' (45 '-' is 0) */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-57 '0'-'9' (61 '=' + is 0) */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 65-79 'A'-'O' */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 80-90 'P'-'Z' */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 97-111 'a'-'o' */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-122 'p'-'z' */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +static const size_t kBase64TriggerLen = 8; + +static BROTLI_INLINE BROTLI_BOOL IsBase64Char(uint8_t c) { + return TO_BROTLI_BOOL(kIsBase64[c]); +} + +static BROTLI_INLINE BROTLI_BOOL MatchTrigger(const uint8_t* ringbuffer, + size_t mask, size_t pos) { + const char* trigger = ";base64,"; + size_t i; + for (i = 0; i < kBase64TriggerLen; ++i) { + if (ringbuffer[(pos + i) & mask] != trigger[i]) return BROTLI_FALSE; + } + return BROTLI_TRUE; +} + +static const uint64_t kBase64Trigger64 = + BROTLI_MAKE_UINT64_T(0x2C343665u, 0x7361623Bu); + +static size_t FindNextBase64Trigger(const uint8_t* ringbuffer, size_t mask, + size_t pos, size_t end) { + while (pos + kBase64TriggerLen <= end) { + size_t ringbuffer_size = mask + 1; + size_t pos_index = pos & mask; + size_t contiguous_len = ringbuffer_size - pos_index; + size_t max_scan_len = end - pos; + size_t scan_len = BROTLI_MIN(size_t, contiguous_len, max_scan_len); + + const uint8_t* p = + (const uint8_t*)memchr(&ringbuffer[pos_index], ';', scan_len); + if (p != NULL) { + size_t offset = (size_t)(p - &ringbuffer[pos_index]); + if (offset + kBase64TriggerLen <= scan_len) { + if (BROTLI_UNALIGNED_LOAD64LE(p) == kBase64Trigger64) { + return pos + offset; + } + } else if (pos + offset + kBase64TriggerLen <= end) { + if (MatchTrigger(ringbuffer, mask, pos + offset)) { + return pos + offset; + } + } else { + return end; + } + pos += offset + 1; + } else { + pos += scan_len; + } + } + return end; +} + +static BROTLI_INLINE BROTLI_BOOL CompareRingbuffer( + const uint8_t* ringbuffer, size_t mask, + size_t pos1, size_t pos2, size_t len) { + size_t idx1 = pos1 & mask; + size_t idx2 = pos2 & mask; + if (idx1 + len <= mask + 1 && idx2 + len <= mask + 1) { + return TO_BROTLI_BOOL( + memcmp(&ringbuffer[idx1], &ringbuffer[idx2], len) == 0); + } + while (len > 0) { + size_t chunk1 = mask + 1 - (pos1 & mask); + size_t chunk2 = mask + 1 - (pos2 & mask); + size_t chunk = BROTLI_MIN(size_t, len, BROTLI_MIN(size_t, chunk1, chunk2)); + if (memcmp(&ringbuffer[pos1 & mask], &ringbuffer[pos2 & mask], chunk) != 0) { + return BROTLI_FALSE; + } + pos1 += chunk; + pos2 += chunk; + len -= chunk; + } + return BROTLI_TRUE; +} + #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif @@ -120,6 +212,11 @@ #include "backward_references_inc.h" #undef HASHER +#define HASHER() H59 +/* NOLINTNEXTLINE(build/include) */ +#include "backward_references_opt_inc.h" +#undef HASHER + #define HASHER() H68 /* NOLINTNEXTLINE(build/include) */ #include "backward_references_inc.h" @@ -172,6 +269,10 @@ /* NOLINTNEXTLINE(build/include) */ #include "backward_references_inc.h" #undef HASHER +#define HASHER() H59 +/* NOLINTNEXTLINE(build/include) */ +#include "backward_references_opt_inc.h" +#undef HASHER #define HASHER() H68 /* NOLINTNEXTLINE(build/include) */ #include "backward_references_inc.h" @@ -206,6 +307,7 @@ CASE_(6) #if defined(BROTLI_MAX_SIMD_QUALITY) CASE_(58) + CASE_(59) CASE_(68) #endif CASE_(40)
diff --git a/enc/backward_references_hq.c b/enc/backward_references_hq.c index 07e82d1..a4718b5 100644 --- a/enc/backward_references_hq.c +++ b/enc/backward_references_hq.c
@@ -433,8 +433,6 @@ const CompoundDictionary* addon = ¶ms->dictionary.compound; size_t gap = addon->total_size; - BROTLI_DCHECK(cur_ix_masked + max_len <= ringbuffer_mask + 1); - EvaluateNode(block_start + stream_offset, pos, max_backward_limit, gap, starting_dist_cache, model, queue, nodes);
diff --git a/enc/backward_references_inc.h b/enc/backward_references_inc.h index 752c12e..7238fca 100644 --- a/enc/backward_references_inc.h +++ b/enc/backward_references_inc.h
@@ -35,7 +35,145 @@ FN(PrepareDistanceCache)(privat, dist_cache); + size_t next_base64_pos = pos_end; + /* End of a payload whose trigger was covered by a backward reference. */ + size_t base64_end = 0; + if (params->base64_mode && + hasher->common.num_base64_regions < params->max_base64_regions) { + next_base64_pos = + FindNextBase64Trigger(ringbuffer, ringbuffer_mask, position, pos_end); + } while (position + FN(HashTypeLength)() < pos_end) { + if (position >= next_base64_pos) { + if (position > next_base64_pos) { + /* A previous backward match jumped over the trigger position. Note + where that payload ends, then find the next trigger. */ + size_t payload_end = next_base64_pos + kBase64TriggerLen; + while (payload_end < pos_end && + IsBase64Char(ringbuffer[payload_end & ringbuffer_mask])) { + payload_end++; + } + if (payload_end - (next_base64_pos + kBase64TriggerLen) >= + params->min_base64_region_len) { + base64_end = payload_end; + } + next_base64_pos = FindNextBase64Trigger(ringbuffer, ringbuffer_mask, + BROTLI_MAX(size_t, position, payload_end), pos_end); + if (position < next_base64_pos) { + goto skip_base64_trigger; + } + } + /* Find where it ends */ + size_t scan_pos = position + kBase64TriggerLen; + size_t first_equal_pos = 0; + while (scan_pos < pos_end) { + uint8_t c = ringbuffer[scan_pos & ringbuffer_mask]; + if (IsBase64Char(c)) { + if (first_equal_pos != 0) { + scan_pos = first_equal_pos; + break; + } + scan_pos++; + } else if (c == '=') { + if (first_equal_pos == 0) { + first_equal_pos = scan_pos; + } + scan_pos++; + } else { + break; + } + } + /* Jump directly to the end of base64 block */ + size_t start_pos = position + kBase64TriggerLen; + size_t length = scan_pos - start_pos; + /* Exclude '=' characters from the flat 6-bit entropy block */ + while (length > 0 && + ringbuffer[(start_pos + length - 1) & ringbuffer_mask] == '=') { + length--; + } + BROTLI_BOOL is_dupe = BROTLI_FALSE; + size_t copy_len = scan_pos - position; + size_t dupe_dist = 0; + if (length > 0 && hasher->common.num_base64_regions > 0) { + size_t r = hasher->common.num_base64_regions; + while (r > 0) { + --r; + size_t prev_start = + hasher->common.base64_regions[r].start_literal_pos; + size_t prev_length = hasher->common.base64_regions[r].length; + if (prev_length != length) { + continue; + } + size_t prev_pos = prev_start - kBase64TriggerLen; + size_t distance = position - prev_pos; + if (distance == 0 || distance > max_backward_limit) { + continue; + } + if (CompareRingbuffer(ringbuffer, ringbuffer_mask, prev_pos, position, + copy_len)) { + is_dupe = BROTLI_TRUE; + dupe_dist = distance; + break; + } + } + } + if (is_dupe) { + size_t dictionary_start = + BROTLI_MIN(size_t, position + position_offset, max_backward_limit); + size_t distance_code = + ComputeDistanceCode(dupe_dist, dictionary_start + gap, dist_cache); + if ((dupe_dist <= (dictionary_start + gap)) && distance_code > 0) { + dist_cache[3] = dist_cache[2]; + dist_cache[2] = dist_cache[1]; + dist_cache[1] = dist_cache[0]; + dist_cache[0] = (int)dupe_dist; + FN(PrepareDistanceCache)(privat, dist_cache); + } + InitCommand(commands++, ¶ms->dist, insert_length, copy_len, 0, + distance_code); + *num_literals += insert_length; + insert_length = 0; + position = scan_pos; + apply_random_heuristics = + position + 2 * copy_len + random_heuristics_window_size; + if (hasher->common.num_base64_regions < params->max_base64_regions) { + next_base64_pos = FindNextBase64Trigger(ringbuffer, ringbuffer_mask, + position, pos_end); + } else { + next_base64_pos = pos_end; + } + continue; + } + if (length >= params->min_base64_region_len && length > 0) { + hasher->common.base64_regions[hasher->common.num_base64_regions] + .start_literal_pos = start_pos; + hasher->common.base64_regions[hasher->common.num_base64_regions] + .length = length; + hasher->common.num_base64_regions++; + insert_length += (scan_pos - position); + position = scan_pos; + if (hasher->common.num_base64_regions < params->max_base64_regions) { + next_base64_pos = FindNextBase64Trigger(ringbuffer, ringbuffer_mask, + position, pos_end); + } else { + next_base64_pos = pos_end; + } + continue; + } else { + next_base64_pos = FindNextBase64Trigger(ringbuffer, ringbuffer_mask, + position + 1, pos_end); + goto skip_base64_trigger; + } + } + skip_base64_trigger:; + if (position < base64_end && position > apply_random_heuristics) { + /* No backward reference for a while inside the payload: the rest of it + is high-entropy (e.g. image data). Emit it as literals without + searching. */ + insert_length += base64_end - position; + position = base64_end; + continue; + } size_t max_length = pos_end - position; size_t max_distance = BROTLI_MIN(size_t, position, max_backward_limit); size_t dictionary_start = BROTLI_MIN(size_t, @@ -59,7 +197,7 @@ FN(FindLongestMatch)(privat, params->dictionary.contextual.dict[dict_id], ringbuffer, ringbuffer_mask, dist_cache, position, max_length, max_distance, dictionary_start + gap, params->dist.max_distance, &sr); - if (ENABLE_COMPOUND_DICTIONARY) { + if (ENABLE_COMPOUND_DICTIONARY && position >= base64_end) { LookupCompoundDictionaryMatch(¶ms->dictionary.compound, ringbuffer, ringbuffer_mask, dist_cache, position, max_length, dictionary_start, params->dist.max_distance, &sr); @@ -90,7 +228,7 @@ ringbuffer, ringbuffer_mask, dist_cache, position + 1, max_length, max_distance, dictionary_start + gap, params->dist.max_distance, &sr2); - if (ENABLE_COMPOUND_DICTIONARY) { + if (ENABLE_COMPOUND_DICTIONARY && position + 1 >= base64_end) { LookupCompoundDictionaryMatch( ¶ms->dictionary.compound, ringbuffer, ringbuffer_mask, dist_cache, position + 1, max_length,
diff --git a/enc/backward_references_opt_inc.h b/enc/backward_references_opt_inc.h new file mode 100644 index 0000000..3ad6755 --- /dev/null +++ b/enc/backward_references_opt_inc.h
@@ -0,0 +1,265 @@ +/* NOLINT(build/header_guard) */ +/* Copyright 2013 Google Inc. All Rights Reserved. + + Distributed under MIT license. + See file LICENSE for detail or copy at https://opensource.org/licenses/MIT +*/ + +/* template parameters: EXPORT_FN, FN */ + +static BROTLI_NOINLINE void EXPORT_FN(CreateBackwardReferences)( + size_t num_bytes, size_t position, + const uint8_t* ringbuffer, size_t ringbuffer_mask, + ContextLut literal_context_lut, const BrotliEncoderParams* params, + Hasher* hasher, int* dist_cache, size_t* last_insert_len, + Command* commands, size_t* num_commands, size_t* num_literals) { + HASHER()* privat = &hasher->privat.FN(_); + /* Set maximum distance, see section 9.1. of the spec. */ + const size_t max_backward_limit = BROTLI_MAX_BACKWARD_LIMIT(params->lgwin); + const size_t position_offset = params->stream_offset; + + const Command* const orig_commands = commands; + size_t insert_length = *last_insert_len; + const size_t pos_end = position + num_bytes; + const size_t store_end = num_bytes >= FN(StoreLookahead)() ? + position + num_bytes - FN(StoreLookahead)() + 1 : position; + + /* For speed up heuristics for random data. */ + const size_t random_heuristics_window_size = + LiteralSpreeLengthForSparseSearch(params); + size_t apply_random_heuristics = position + random_heuristics_window_size; + const size_t gap = params->dictionary.compound.total_size; + /* Compound-dictionary probe state: written by the prefetch helper before + each FindLongestMatch, consumed by the lookup after it. */ + PreparedDictionaryProbe dict_probes[SHARED_BROTLI_MAX_COMPOUND_DICTS + 1]; + + /* Minimum score to accept a backward reference. */ + const score_t kMinScore = BROTLI_SCORE_BASE + 100; + + FN(PrepareDistanceCache)(privat, dist_cache); + + size_t next_base64_pos = pos_end; + if (params->base64_mode && + hasher->common.num_base64_regions < params->max_base64_regions) { + next_base64_pos = + FindNextBase64Trigger(ringbuffer, ringbuffer_mask, position, pos_end); + } + while (position + FN(HashTypeLength)() < pos_end) { + if (position >= next_base64_pos) { + /* Find where it ends */ + size_t scan_pos = position + kBase64TriggerLen; + size_t first_equal_pos = 0; + while (scan_pos < pos_end) { + uint8_t c = ringbuffer[scan_pos & ringbuffer_mask]; + if (IsBase64Char(c)) { + if (first_equal_pos != 0) { + scan_pos = first_equal_pos; + break; + } + scan_pos++; + } else if (c == '=') { + if (first_equal_pos == 0) { + first_equal_pos = scan_pos; + } + scan_pos++; + } else { + break; + } + } + /* Jump directly to the end of base64 block */ + /* Skip the ';base64,' trigger */ + size_t start_pos = position + kBase64TriggerLen; + size_t length = scan_pos - start_pos; + /* Exclude '=' characters from the flat 6-bit entropy block */ + while (length > 0 && + ringbuffer[(start_pos + length - 1) & ringbuffer_mask] == '=') { + length--; + } + if (length > 0) { + hasher->common.base64_regions[hasher->common.num_base64_regions] + .start_literal_pos = start_pos; + hasher->common.base64_regions[hasher->common.num_base64_regions] + .length = length; + hasher->common.num_base64_regions++; + } + insert_length += (scan_pos - position); + position = scan_pos; + if (hasher->common.num_base64_regions < params->max_base64_regions) { + next_base64_pos = FindNextBase64Trigger(ringbuffer, ringbuffer_mask, + position, pos_end); + } else { + next_base64_pos = pos_end; + } + continue; + } + size_t max_length = pos_end - position; + size_t max_distance = BROTLI_MIN(size_t, position, max_backward_limit); + size_t dictionary_start = BROTLI_MIN(size_t, + position + position_offset, max_backward_limit); + HasherSearchResult sr; + int dict_id = 0; + uint8_t p1 = 0; + uint8_t p2 = 0; + if (params->dictionary.contextual.context_based) { + p1 = position >= 1 ? + ringbuffer[(size_t)(position - 1) & ringbuffer_mask] : 0; + p2 = position >= 2 ? + ringbuffer[(size_t)(position - 2) & ringbuffer_mask] : 0; + dict_id = params->dictionary.contextual.context_map[ + BROTLI_CONTEXT(p1, p2, literal_context_lut)]; + } + sr.len = 0; + sr.len_code_delta = 0; + sr.distance = 0; + sr.score = kMinScore; + if (ENABLE_COMPOUND_DICTIONARY) { + PrefetchCompoundDictionaryMatchOpt(¶ms->dictionary.compound, + ringbuffer, ringbuffer_mask, position, dict_probes); + } + FN(FindLongestMatch)(privat, params->dictionary.contextual.dict[dict_id], + ringbuffer, ringbuffer_mask, dist_cache, position, max_length, + max_distance, dictionary_start + gap, params->dist.max_distance, &sr); + if (ENABLE_COMPOUND_DICTIONARY) { + LookupCompoundDictionaryMatchOpt( + ¶ms->dictionary.compound, dict_probes, ringbuffer, + ringbuffer_mask, dist_cache, position, max_length, dictionary_start, + params->dist.max_distance, &sr); + } + if (sr.score > kMinScore) { + /* Found a match. Let's look for something even better ahead. */ + int delayed_backward_references_in_row = 0; + --max_length; + for (;; --max_length) { + const score_t cost_diff_lazy = 175; + HasherSearchResult sr2; + sr2.len = + params->quality < MIN_QUALITY_FOR_EXTENSIVE_REFERENCE_SEARCH + ? BROTLI_MIN(size_t, sr.len - 1, max_length) + : BROTLI_MIN(size_t, + MinimumBetterLength(sr.score + cost_diff_lazy - 1), + max_length); + sr2.len_code_delta = 0; + sr2.distance = 0; + sr2.score = kMinScore; + max_distance = BROTLI_MIN(size_t, position + 1, max_backward_limit); + dictionary_start = BROTLI_MIN(size_t, + position + 1 + position_offset, max_backward_limit); + if (params->dictionary.contextual.context_based) { + p2 = p1; + p1 = ringbuffer[position & ringbuffer_mask]; + dict_id = params->dictionary.contextual.context_map[ + BROTLI_CONTEXT(p1, p2, literal_context_lut)]; + } + if (ENABLE_COMPOUND_DICTIONARY) { + PrefetchCompoundDictionaryMatchOpt(¶ms->dictionary.compound, + ringbuffer, ringbuffer_mask, position + 1, dict_probes); + } + FN(FindLongestMatch)(privat, + params->dictionary.contextual.dict[dict_id], + ringbuffer, ringbuffer_mask, dist_cache, position + 1, max_length, + max_distance, dictionary_start + gap, params->dist.max_distance, + &sr2); + if (ENABLE_COMPOUND_DICTIONARY) { + LookupCompoundDictionaryMatchOpt( + ¶ms->dictionary.compound, dict_probes, ringbuffer, + ringbuffer_mask, dist_cache, position + 1, max_length, + dictionary_start, params->dist.max_distance, &sr2); + } + if (sr2.score >= sr.score + cost_diff_lazy) { + /* Ok, let's just write one byte for now and start a match from the + next byte. */ + ++position; + ++insert_length; + sr = sr2; + if (++delayed_backward_references_in_row < 4 && + position + FN(HashTypeLength)() < pos_end) { + continue; + } + } + break; + } + apply_random_heuristics = + position + 2 * sr.len + random_heuristics_window_size; + dictionary_start = BROTLI_MIN(size_t, + position + position_offset, max_backward_limit); + { + /* The first 16 codes are special short-codes, + and the minimum offset is 1. */ + size_t distance_code = ComputeDistanceCode( + sr.distance, dictionary_start + gap, dist_cache); + if ((sr.distance <= (dictionary_start + gap)) && distance_code > 0) { + dist_cache[3] = dist_cache[2]; + dist_cache[2] = dist_cache[1]; + dist_cache[1] = dist_cache[0]; + dist_cache[0] = (int)sr.distance; + FN(PrepareDistanceCache)(privat, dist_cache); + } + InitCommand(commands++, ¶ms->dist, insert_length, + sr.len, sr.len_code_delta, distance_code); + } + *num_literals += insert_length; + insert_length = 0; + /* Put the hash keys into the table, if there are enough bytes left. + Depending on the hasher implementation, it can push all positions + in the given range or only a subset of them. + Avoid hash poisoning with RLE data. */ + { + size_t range_start = position + 2; + size_t range_end = BROTLI_MIN(size_t, position + sr.len, store_end); + if (sr.distance < (sr.len >> 2)) { + range_start = BROTLI_MIN(size_t, range_end, BROTLI_MAX(size_t, + range_start, position + sr.len - (sr.distance << 2))); + } + /* The next search is at position + sr.len (the one-ahead in the + prefetch helper covers only the cur_ix + 1 successor): prefetch its + dictionary heads[] line before the StoreRange loop. */ + if (ENABLE_COMPOUND_DICTIONARY) { + PrefetchCompoundDictionaryHeadsOpt(¶ms->dictionary.compound, + ringbuffer, ringbuffer_mask, range_end); + } + FN(StoreRange)(privat, ringbuffer, ringbuffer_mask, range_start, + range_end); + } + position += sr.len; + } else { + ++insert_length; + ++position; + /* If we have not seen matches for a long time, we can skip some + match lookups. Unsuccessful match lookups are very very expensive + and this kind of a heuristic speeds up compression quite + a lot. */ + if (position > apply_random_heuristics) { + /* Going through uncompressible data, jump. */ + if (position > + apply_random_heuristics + 4 * random_heuristics_window_size) { + /* It is quite a long time since we saw a copy, so we assume + that this data is not compressible, and store hashes less + often. Hashes of non compressible data are less likely to + turn out to be useful in the future, too, so we store less of + them to not to flood out the hash table of good compressible + data. */ + const size_t kMargin = + BROTLI_MAX(size_t, FN(StoreLookahead)() - 1, 4); + size_t pos_jump = + BROTLI_MIN(size_t, position + 16, pos_end - kMargin); + for (; position < pos_jump; position += 4) { + FN(Store)(privat, ringbuffer, ringbuffer_mask, position); + insert_length += 4; + } + } else { + const size_t kMargin = + BROTLI_MAX(size_t, FN(StoreLookahead)() - 1, 2); + size_t pos_jump = + BROTLI_MIN(size_t, position + 8, pos_end - kMargin); + for (; position < pos_jump; position += 2) { + FN(Store)(privat, ringbuffer, ringbuffer_mask, position); + insert_length += 2; + } + } + } + } + } + insert_length += pos_end - position; + *last_insert_len = insert_length; + *num_commands += (size_t)(commands - orig_commands); +}
diff --git a/enc/block_encoder_inc.h b/enc/block_encoder_inc.h index 8cbd5ea..5a90658 100644 --- a/enc/block_encoder_inc.h +++ b/enc/block_encoder_inc.h
@@ -1,20 +1,14 @@ -/* NOLINT(build/header_guard) */ -/* Copyright 2014 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 */ +#if !defined(BROTLI_BASE64_LUT_) +#define BROTLI_BASE64_LUT_ +BROTLI_INTERNAL extern const BROTLI_MODEL("small") uint8_t kIsBase64[256]; +#endif #define HistogramType FN(Histogram) -/* Creates entropy codes for all block types and stores them to the bit - stream. */ -static void FN(BuildAndStoreEntropyCodes)(MemoryManager* m, BlockEncoder* self, - const HistogramType* histograms, const size_t histograms_size, - const size_t alphabet_size, HuffmanTree* tree, - size_t* storage_ix, uint8_t* storage) { +static void FN(BuildAndStoreEntropyCodes)( + MemoryManager* m, BlockEncoder* self, const HistogramType* histograms, + const size_t histograms_size, const size_t alphabet_size, HuffmanTree* tree, + const uint8_t* is_base64_histogram, size_t* storage_ix, uint8_t* storage) { const size_t table_size = histograms_size * self->histogram_length_; self->depths_ = BROTLI_ALLOC(m, uint8_t, table_size); self->bits_ = BROTLI_ALLOC(m, uint16_t, table_size); @@ -24,9 +18,37 @@ size_t i; for (i = 0; i < histograms_size; ++i) { size_t ix = i * self->histogram_length_; - BuildAndStoreHuffmanTree(&histograms[i].data_[0], self->histogram_length_, - alphabet_size, tree, &self->depths_[ix], &self->bits_[ix], - storage_ix, storage); + if (self->histogram_length_ == 256 && is_base64_histogram && i < 256 && + is_base64_histogram[i]) { + size_t k; + BROTLI_BOOL all_base64 = BROTLI_TRUE; + for (k = 0; k < 256; ++k) { + if (histograms[i].data_[k] > 0 && !kIsBase64[k]) { + all_base64 = BROTLI_FALSE; + break; + } + } + if (all_base64) { + memset(&self->depths_[ix], 0, 256); + for (k = 0; k < 256; ++k) { + if (kIsBase64[k]) { + self->depths_[ix + k] = 6; + } + } + BrotliConvertBitDepthsToSymbols(&self->depths_[ix], 256, + &self->bits_[ix]); + BrotliStoreHuffmanTree(&self->depths_[ix], 256, tree, storage_ix, + storage); + } else { + BuildAndStoreHuffmanTree( + &histograms[i].data_[0], self->histogram_length_, alphabet_size, + tree, &self->depths_[ix], &self->bits_[ix], storage_ix, storage); + } + } else { + BuildAndStoreHuffmanTree( + &histograms[i].data_[0], self->histogram_length_, alphabet_size, + tree, &self->depths_[ix], &self->bits_[ix], storage_ix, storage); + } } } }
diff --git a/enc/brotli_bit_stream.c b/enc/brotli_bit_stream.c index 1e9e946..10727e0 100644 --- a/enc/brotli_bit_stream.c +++ b/enc/brotli_bit_stream.c
@@ -1017,16 +1017,44 @@ if (BROTLI_IS_OOM(m)) return; } - BuildAndStoreEntropyCodesLiteral(m, literal_enc, mb->literal_histograms, - mb->literal_histograms_size, BROTLI_NUM_LITERAL_SYMBOLS, tree, - storage_ix, storage); + { + uint8_t is_base64_histogram[256] = {0}; + size_t max_type_id = BROTLI_MIN(size_t, mb->literal_split.num_types, 256); + size_t type_id; + for (type_id = 0; type_id < max_type_id; ++type_id) { + if (mb->literal_is_base64[type_id >> 3] & (1u << (type_id & 7))) { + if (mb->literal_context_map) { + size_t j; + for (j = 0; j < (1u << BROTLI_LITERAL_CONTEXT_BITS); ++j) { + uint32_t b64_histo_id = + mb->literal_context_map[(type_id << BROTLI_LITERAL_CONTEXT_BITS) + j]; + if (b64_histo_id < 256) { + is_base64_histogram[b64_histo_id] = 1; + } + } + } else { + uint32_t b64_histo_id = (uint32_t)type_id; + if (b64_histo_id < 256) { + is_base64_histogram[b64_histo_id] = 1; + } + } + } + } + + BuildAndStoreEntropyCodesLiteral(m, literal_enc, mb->literal_histograms, + mb->literal_histograms_size, BROTLI_NUM_LITERAL_SYMBOLS, tree, + is_base64_histogram, + storage_ix, storage); + } if (BROTLI_IS_OOM(m)) return; BuildAndStoreEntropyCodesCommand(m, command_enc, mb->command_histograms, mb->command_histograms_size, BROTLI_NUM_COMMAND_SYMBOLS, tree, + NULL, storage_ix, storage); if (BROTLI_IS_OOM(m)) return; BuildAndStoreEntropyCodesDistance(m, distance_enc, mb->distance_histograms, mb->distance_histograms_size, num_distance_symbols, tree, + NULL, storage_ix, storage); if (BROTLI_IS_OOM(m)) return; BROTLI_FREE(m, tree);
diff --git a/enc/compound_dictionary.c b/enc/compound_dictionary.c index bf323a4..23166c8 100644 --- a/enc/compound_dictionary.c +++ b/enc/compound_dictionary.c
@@ -155,6 +155,9 @@ uint32_t hash_bits = 40; uint16_t bucket_limit = 32; size_t volume = 16u << bucket_bits; + if (source_size > SHARED_BROTLI_MAX_RAW_DICT_SIZE) { + return NULL; + } /* Tune parameters to fit dictionary size. */ while (volume < source_size && bucket_bits < 22) { bucket_bits++; @@ -183,6 +186,9 @@ if (!dictionary) return BROTLI_FALSE; length = dictionary->source_size; + if (length > SHARED_BROTLI_MAX_RAW_DICT_SIZE - compound->total_size) { + return BROTLI_FALSE; + } index = compound->num_chunks; compound->total_size += length; compound->chunks[index] = dictionary; @@ -199,6 +205,17 @@ compound->chunk_source[index] = (const uint8_t*)BROTLI_UNALIGNED_LOAD_PTR((const uint8_t**)tail); } + { + PreparedDictionaryView* view = &compound->chunk_views[index]; + view->slot_offsets = slot_offsets; + view->heads = heads; + view->items = items; + view->source = compound->chunk_source[index]; + view->source_size = dictionary->source_size; + view->hash_shift = 64u - dictionary->bucket_bits; + view->slot_mask = (~((uint32_t)0U)) >> (32 - dictionary->slot_bits); + view->hash_mask = (~((uint64_t)0U)) >> (64 - dictionary->hash_bits); + } } compound->num_chunks++; return BROTLI_TRUE;
diff --git a/enc/compound_dictionary.h b/enc/compound_dictionary.h index 686f92f..240a7f9 100644 --- a/enc/compound_dictionary.h +++ b/enc/compound_dictionary.h
@@ -51,12 +51,28 @@ BROTLI_INTERNAL void DestroyPreparedDictionary(MemoryManager* m, PreparedDictionary* dictionary); +/* A derived view of a PreparedDictionary, optimized for faster lookup. Used by + * the optimized H59 hasher. Its fields are a pure function of + * PreparedDictionary's so we can generate it at attach time. */ +typedef struct PreparedDictionaryView { + const uint32_t* slot_offsets; + const uint16_t* heads; + const uint32_t* items; + const uint8_t* source; + uint32_t source_size; + uint32_t hash_shift; /* 64 - bucket_bits */ + uint32_t slot_mask; + uint64_t hash_mask; +} PreparedDictionaryView; + typedef struct CompoundDictionary { /* LZ77 prefix, compound dictionary */ size_t num_chunks; size_t total_size; /* Client instances. */ const PreparedDictionary* chunks[SHARED_BROTLI_MAX_COMPOUND_DICTS + 1]; + /* TODO: const? */ + PreparedDictionaryView chunk_views[SHARED_BROTLI_MAX_COMPOUND_DICTS + 1]; const uint8_t* chunk_source[SHARED_BROTLI_MAX_COMPOUND_DICTS + 1]; size_t chunk_offsets[SHARED_BROTLI_MAX_COMPOUND_DICTS + 1];
diff --git a/enc/encode.c b/enc/encode.c index 9e72744..44b447a 100644 --- a/enc/encode.c +++ b/enc/encode.c
@@ -105,6 +105,28 @@ state->params.stream_offset = value; return BROTLI_TRUE; + case BROTLI_PARAM_BASE64_MODE: + state->params.base64_mode = (int)(value & 1); + return BROTLI_TRUE; + + case BROTLI_PARAM_MAX_BASE64_REGIONS: + state->params.max_base64_regions = value; + return BROTLI_TRUE; + + case BROTLI_PARAM_MIN_BASE64_REGION_LEN: + state->params.min_base64_region_len = value; + return BROTLI_TRUE; + + case BROTLI_PARAM_SIMD_HASHER: + if (value > 2) return BROTLI_FALSE; + state->params.simd_hasher = (BrotliEncoderSimdHasher)value; + return BROTLI_TRUE; + + case BROTLI_PARAM_HASHER_OPT: + if ((value != 0) && (value != 1)) return BROTLI_FALSE; + state->params.hasher_opt = TO_BROTLI_BOOL(value); + return BROTLI_TRUE; + default: return BROTLI_FALSE; } } @@ -488,6 +510,8 @@ const uint64_t last_flush_pos, const size_t bytes, const BROTLI_BOOL is_last, + const Base64Region* base64_regions, + size_t num_base64_regions, ContextType literal_context_mode, const BrotliEncoderParams* params, const uint8_t prev_byte, @@ -556,11 +580,14 @@ BROTLI_FREE(m, arena); } BrotliBuildMetaBlockGreedy(m, data, wrapped_last_flush_pos, mask, + base64_regions, num_base64_regions, prev_byte, prev_byte2, literal_context_lut, num_literal_contexts, literal_context_map, commands, num_commands, &mb); if (BROTLI_IS_OOM(m)) return; } else { - BrotliBuildMetaBlock(m, data, wrapped_last_flush_pos, mask, &block_params, + BrotliBuildMetaBlock(m, data, wrapped_last_flush_pos, mask, + base64_regions, num_base64_regions, + &block_params, prev_byte, prev_byte2, commands, num_commands, literal_context_mode, @@ -688,6 +715,11 @@ params->size_hint = 0; params->disable_literal_context_modeling = BROTLI_FALSE; BrotliInitSharedEncoderDictionary(¶ms->dictionary); + params->base64_mode = (int)BROTLI_DEFAULT_BASE64_MODE; + params->max_base64_regions = BROTLI_DEFAULT_MAX_BASE64_REGIONS; + params->min_base64_region_len = BROTLI_DEFAULT_MIN_BASE64_REGION_LEN; + params->simd_hasher = BROTLI_DEFAULT_SIMD_HASHER; + params->hasher_opt = BROTLI_FALSE; params->dist.distance_postfix_bits = 0; params->dist.num_direct_distance_codes = 0; params->dist.alphabet_size_max = @@ -757,15 +789,17 @@ /* Save the state of the distance cache in case we need to restore it for emitting an uncompressed block. */ memcpy(s->saved_dist_cache_, s->dist_cache_, sizeof(s->saved_dist_cache_)); + s->hasher_.common.num_base64_regions = 0; } BrotliEncoderState* BrotliEncoderCreateInstance( brotli_alloc_func alloc_func, brotli_free_func free_func, void* opaque) { + BrotliEncoderState* state; BROTLI_BOOL healthy = BrotliEncoderEnsureStaticInit(); if (!healthy) { return 0; } - BrotliEncoderState* state = (BrotliEncoderState*)BrotliBootstrapAlloc( + state = (BrotliEncoderState*)BrotliBootstrapAlloc( sizeof(BrotliEncoderState), alloc_func, free_func, opaque); if (state == NULL) { /* BROTLI_DUMP(); */ @@ -1169,10 +1203,12 @@ storage[1] = (uint8_t)(s->last_bytes_ >> 8); WriteMetaBlockInternal( m, data, mask, s->last_flush_pos_, metablock_size, is_last, + s->hasher_.common.base64_regions, s->hasher_.common.num_base64_regions, literal_context_mode, &s->params, s->prev_byte_, s->prev_byte2_, s->num_literals_, s->num_commands_, s->commands_, s->saved_dist_cache_, s->dist_cache_, &storage_ix, storage); if (BROTLI_IS_OOM(m)) return BROTLI_FALSE; + s->hasher_.common.num_base64_regions = 0; s->last_bytes_ = (uint16_t)(storage[storage_ix >> 3]); s->last_bytes_bits_ = storage_ix & 7u; s->last_flush_pos_ = s->input_pos_;
diff --git a/enc/hash.h b/enc/hash.h index eb9e455..0340611 100644 --- a/enc/hash.h +++ b/enc/hash.h
@@ -29,6 +29,11 @@ #endif typedef struct { + size_t start_literal_pos; + size_t length; +} Base64Region; + +typedef struct { /** * Dynamically allocated areas; regular hasher uses one or two allocations; * "composite" hasher uses up to 4 allocations. @@ -52,6 +57,9 @@ * data initialization (using input ringbuffer). */ BROTLI_BOOL is_prepared_; + + Base64Region* base64_regions; + size_t num_base64_regions; } HasherCommon; #define score_t size_t @@ -118,6 +126,13 @@ BROTLI_DISTANCE_BIT_PENALTY * Log2FloorNonZero(backward_reference_offset); } +/* Returns the minimum length of a backward reference that will improve on the + provided score. We conservatively assume that the match will be a last + distance match, the best case scenario for the next match.*/ +static BROTLI_INLINE size_t MinimumBetterLength(score_t score) { + return (score - (BROTLI_SCORE_BASE + 15)) / BROTLI_LITERAL_BYTE_SCORE; +} + static BROTLI_INLINE score_t BackwardReferenceScoreUsingLastDistance( size_t copy_length) { return BROTLI_LITERAL_BYTE_SCORE * (score_t)copy_length + @@ -283,6 +298,10 @@ #include "hash_longest_match_simd_inc.h" /* NOLINT(build/include) */ #undef HASHER +#define HASHER() H59 +#include "hash_longest_match_simd_opt_inc.h" /* NOLINT(build/include) */ +#undef HASHER + #define HASHER() H68 #include "hash_longest_match64_simd_inc.h" /* NOLINT(build/include) */ #undef HASHER @@ -381,7 +400,7 @@ #if defined(BROTLI_MAX_SIMD_QUALITY) #define FOR_SIMPLE_HASHERS(H) \ - H(2) H(3) H(4) H(5) H(6) H(40) H(41) H(42) H(54) H(58) H(68) + H(2) H(3) H(4) H(5) H(6) H(40) H(41) H(42) H(54) H(58) H(59) H(68) #else #define FOR_SIMPLE_HASHERS(H) \ H(2) H(3) H(4) H(5) H(6) H(40) H(41) H(42) H(54) @@ -408,6 +427,7 @@ hasher->common.extra[1] = NULL; hasher->common.extra[2] = NULL; hasher->common.extra[3] = NULL; + hasher->common.base64_regions = NULL; } static BROTLI_INLINE void DestroyHasher(MemoryManager* m, Hasher* hasher) { @@ -415,6 +435,9 @@ if (hasher->common.extra[1] != NULL) BROTLI_FREE(m, hasher->common.extra[1]); if (hasher->common.extra[2] != NULL) BROTLI_FREE(m, hasher->common.extra[2]); if (hasher->common.extra[3] != NULL) BROTLI_FREE(m, hasher->common.extra[3]); + if (hasher->common.base64_regions != NULL) { + BROTLI_FREE(m, hasher->common.base64_regions); + } } static BROTLI_INLINE void HasherReset(Hasher* hasher) { @@ -452,6 +475,13 @@ hasher->common.extra[i] = BROTLI_ALLOC(m, uint8_t, alloc_size[i]); if (BROTLI_IS_OOM(m) || BROTLI_IS_NULL(hasher->common.extra[i])) return; } + if (params->base64_mode && params->max_base64_regions > 0) { + hasher->common.base64_regions = BROTLI_ALLOC( + m, Base64Region, params->max_base64_regions); + if (BROTLI_IS_OOM(m) || BROTLI_IS_NULL(hasher->common.base64_regions)) { + return; + } + } switch (hasher->common.params.type) { #define INITIALIZE_(N) \ case N: \ @@ -545,8 +575,6 @@ source = (const uint8_t*)BROTLI_UNALIGNED_LOAD_PTR((const uint8_t**)tail); } - BROTLI_DCHECK(cur_ix_masked + max_length <= ring_buffer_mask + 1); - for (i = 0; i < 4; ++i) { const size_t distance = (size_t)distance_cache[i]; size_t offset; @@ -656,8 +684,6 @@ source = (const uint8_t*)BROTLI_UNALIGNED_LOAD_PTR((const uint8_t**)tail); } - BROTLI_DCHECK(cur_ix_masked + max_length <= ring_buffer_mask + 1); - while (item == 0) { size_t offset; size_t distance; @@ -728,6 +754,188 @@ return total_found; } +/* Struct for handing state between the prefetch and find calls. Avoids + * recalculating the hash and dependent variables. */ +typedef struct PreparedDictionaryProbe { + const uint32_t* chain; + uint32_t item; +} PreparedDictionaryProbe; + +static BROTLI_INLINE void PrefetchCompoundDictionaryMatchOpt( + const CompoundDictionary* addon, const uint8_t* BROTLI_RESTRICT data, + const size_t ring_buffer_mask, const size_t cur_ix, + PreparedDictionaryProbe* BROTLI_RESTRICT probes) { + const size_t cur_ix_masked = cur_ix & ring_buffer_mask; + const uint64_t bytes = BROTLI_UNALIGNED_LOAD64LE(&data[cur_ix_masked]); + size_t d; + for (d = 0; d < addon->num_chunks; ++d) { + const PreparedDictionaryView* view = &addon->chunk_views[d]; + const uint64_t h = + (bytes & view->hash_mask) * kPreparedDictionaryHashMul64Long; + const uint32_t key = (uint32_t)(h >> view->hash_shift); + const uint32_t slot = key & view->slot_mask; + const uint32_t head = view->heads[key]; + /* Deliberately branchless - if head == 0xFFFF (no items), we'll prefetch + * some garbage address. Prefetch can't fault, so this is safe.*/ + const uint32_t* chain = &view->items[view->slot_offsets[slot] + head]; + PREFETCH_L1(chain); + probes[d].chain = chain; + probes[d].item = (head == 0xFFFF) ? 1 : 0; + + /* One-ahead for the heads[] line of the next probe: the next search is + at cur_ix + 1 (the lazy search, or the next position after a miss), + whose hash covers bytes cur_ix+1.. -- the same 8-byte load shifted by + one byte (hash_mask spans at most 56 bits for this to be exact; a + mismatch would only waste the prefetch). The heads[key] load above + misses L1 ~94% of the time (mostly L3 fills). */ + { + const uint64_t h1 = + ((bytes >> 8) & view->hash_mask) * kPreparedDictionaryHashMul64Long; + PREFETCH_L1(&view->heads[(uint32_t)(h1 >> view->hash_shift)]); + } + } +} + +/* The heads[] half of PrefetchCompoundDictionaryMatchOpt: hash the position + and prefetch its heads[] line, with no dependent items[] load. Used at the + commit site, where the successor position is known but the probe state for + it would not survive the intervening StoreRange. */ +static BROTLI_INLINE void PrefetchCompoundDictionaryHeadsOpt( + const CompoundDictionary* addon, const uint8_t* BROTLI_RESTRICT data, + const size_t ring_buffer_mask, const size_t cur_ix) { + const uint64_t bytes = + BROTLI_UNALIGNED_LOAD64LE(&data[cur_ix & ring_buffer_mask]); + size_t d; + for (d = 0; d < addon->num_chunks; ++d) { + const PreparedDictionaryView* view = &addon->chunk_views[d]; + const uint64_t h = + (bytes & view->hash_mask) * kPreparedDictionaryHashMul64Long; + PREFETCH_L1(&view->heads[(uint32_t)(h >> view->hash_shift)]); + } +} + +static BROTLI_INLINE void FindCompoundDictionaryMatchOpt( + const PreparedDictionaryView* self, + const PreparedDictionaryProbe* BROTLI_RESTRICT probe, + const uint8_t* BROTLI_RESTRICT data, + const size_t ring_buffer_mask, const int* BROTLI_RESTRICT distance_cache, + const size_t cur_ix, const size_t max_length, const size_t distance_offset, + const size_t max_distance, HasherSearchResult* BROTLI_RESTRICT out) { + const uint32_t source_size = self->source_size; + const size_t boundary = distance_offset - source_size; + + const uint8_t* source = self->source; + + const size_t cur_ix_masked = cur_ix & ring_buffer_mask; + score_t best_score = out->score; + size_t best_len = out->len; + size_t i; + /* The hash and hashtable offsets were calculated in Prefetch, reuse the + * results. */ + const uint32_t* BROTLI_RESTRICT chain = probe->chain; + uint32_t item = probe->item; +#if defined(BROTLI_DEBUG) || defined(BROTLI_ENABLE_LOG) + { + const uint64_t bytes = BROTLI_UNALIGNED_LOAD64LE(&data[cur_ix_masked]); + const uint64_t h = + (bytes & self->hash_mask) * kPreparedDictionaryHashMul64Long; + const uint32_t key = (uint32_t)(h >> self->hash_shift); + const uint32_t slot = key & self->slot_mask; + const uint32_t head = self->heads[key]; + const uint32_t* expected_chain = + &self->items[self->slot_offsets[slot] + head]; + const uint32_t expected_item = (head == 0xFFFF) ? 1 : 0; + BROTLI_DCHECK(probe->chain == expected_chain); + BROTLI_DCHECK(probe->item == expected_item); + } +#endif + + for (i = 0; i < 4; ++i) { + const size_t distance = (size_t)distance_cache[i]; + size_t offset; + size_t limit; + size_t len; + if (distance <= boundary || distance > distance_offset) continue; + offset = distance_offset - distance; + limit = source_size - offset; + limit = limit > max_length ? max_length : limit; + len = FindMatchLengthWithLimit(&source[offset], &data[cur_ix_masked], + limit); + if (len >= 2) { + score_t score = BackwardReferenceScoreUsingLastDistance(len); + if (best_score < score) { + if (i != 0) score -= BackwardReferencePenaltyUsingLastDistance(i); + if (best_score < score) { + best_score = score; + if (len > best_len) best_len = len; + out->len = len; + out->len_code_delta = 0; + out->distance = distance; + out->score = best_score; + } + } + } + } + /* we require matches of len >4, so increase best_len to 3, so we can compare + * 4 bytes all the time. */ + if (best_len < 3) { + best_len = 3; + } + while (item == 0) { + size_t offset; + size_t distance; + size_t limit; + item = *chain; + chain++; + offset = item & 0x7FFFFFFF; + item &= 0x80000000; + distance = distance_offset - offset; + limit = source_size - offset; + limit = (limit > max_length) ? max_length : limit; + if (distance > max_distance) continue; + if (best_len >= limit || + /* compare 4 bytes ending at best_len + 1 */ + BrotliUnalignedRead32(&data[cur_ix_masked + best_len - 3]) != + BrotliUnalignedRead32(&source[offset + best_len - 3])) { + continue; + } + { + const size_t len = FindMatchLengthWithLimit(&source[offset], + &data[cur_ix_masked], + limit); + if (len >= 4) { + score_t score = BackwardReferenceScore(len, distance); + if (best_score < score) { + best_score = score; + best_len = len; + out->len = best_len; + out->len_code_delta = 0; + out->distance = distance; + out->score = best_score; + } + } + } + } +} + +static BROTLI_INLINE void LookupCompoundDictionaryMatchOpt( + const CompoundDictionary* addon, + const PreparedDictionaryProbe* BROTLI_RESTRICT probes, + const uint8_t* BROTLI_RESTRICT data, + const size_t ring_buffer_mask, const int* BROTLI_RESTRICT distance_cache, + const size_t cur_ix, const size_t max_length, + const size_t max_ring_buffer_distance, const size_t max_distance, + HasherSearchResult* sr) { + size_t base_offset = max_ring_buffer_distance + 1 + addon->total_size - 1; + size_t d; + for (d = 0; d < addon->num_chunks; ++d) { + FindCompoundDictionaryMatchOpt( + &addon->chunk_views[d], &probes[d], data, ring_buffer_mask, + distance_cache, cur_ix, max_length, + base_offset - addon->chunk_offsets[d], max_distance, sr); + } +} + #if defined(__cplusplus) || defined(c_plusplus) } /* extern "C" */ #endif
diff --git a/enc/hash_forgetful_chain_inc.h b/enc/hash_forgetful_chain_inc.h index b653698..31269bc 100644 --- a/enc/hash_forgetful_chain_inc.h +++ b/enc/hash_forgetful_chain_inc.h
@@ -213,8 +213,6 @@ out->len = 0; out->len_code_delta = 0; - BROTLI_DCHECK(cur_ix_masked + max_length <= ring_buffer_mask + 1); - /* Try last distance first. */ for (i = 0; i < NUM_LAST_DISTANCES_TO_CHECK; ++i) { const size_t backward = (size_t)distance_cache[i];
diff --git a/enc/hash_longest_match64_inc.h b/enc/hash_longest_match64_inc.h index 3131451..4e6d7d6 100644 --- a/enc/hash_longest_match64_inc.h +++ b/enc/hash_longest_match64_inc.h
@@ -178,8 +178,6 @@ out->len = 0; out->len_code_delta = 0; - BROTLI_DCHECK(cur_ix_masked + max_length <= ring_buffer_mask + 1); - /* Try last distance first. */ for (i = 0; i < (size_t)self->num_last_distances_to_check_; ++i) { const size_t backward = (size_t)distance_cache[i];
diff --git a/enc/hash_longest_match64_simd_inc.h b/enc/hash_longest_match64_simd_inc.h index 17dc67d..ea16330 100644 --- a/enc/hash_longest_match64_simd_inc.h +++ b/enc/hash_longest_match64_simd_inc.h
@@ -195,8 +195,6 @@ out->len = 0; out->len_code_delta = 0; - BROTLI_DCHECK(cur_ix_masked + max_length <= ring_buffer_mask + 1); - /* Try last distance first. */ for (i = 0; i < (size_t)self->num_last_distances_to_check_; ++i) { const size_t backward = (size_t)distance_cache[i];
diff --git a/enc/hash_longest_match_inc.h b/enc/hash_longest_match_inc.h index 674dced..ddf155f 100644 --- a/enc/hash_longest_match_inc.h +++ b/enc/hash_longest_match_inc.h
@@ -178,8 +178,6 @@ out->len = 0; out->len_code_delta = 0; - BROTLI_DCHECK(cur_ix_masked + max_length <= ring_buffer_mask + 1); - /* Try last distance first. */ for (i = 0; i < (size_t)self->num_last_distances_to_check_; ++i) { const size_t backward = (size_t)distance_cache[i];
diff --git a/enc/hash_longest_match_quickly_inc.h b/enc/hash_longest_match_quickly_inc.h index 12fdb44..8fa8bf9 100644 --- a/enc/hash_longest_match_quickly_inc.h +++ b/enc/hash_longest_match_quickly_inc.h
@@ -165,8 +165,6 @@ size_t cached_backward = (size_t)distance_cache[0]; size_t prev_ix = cur_ix - cached_backward; - BROTLI_DCHECK(cur_ix_masked + max_length <= ring_buffer_mask + 1); - out->len_code_delta = 0; if (prev_ix < cur_ix && cached_backward <= max_backward) { prev_ix &= (uint32_t)ring_buffer_mask;
diff --git a/enc/hash_longest_match_simd_inc.h b/enc/hash_longest_match_simd_inc.h index 075f6da..6b92380 100644 --- a/enc/hash_longest_match_simd_inc.h +++ b/enc/hash_longest_match_simd_inc.h
@@ -170,8 +170,6 @@ out->len = 0; out->len_code_delta = 0; - BROTLI_DCHECK(cur_ix_masked + max_length <= ring_buffer_mask + 1); - /* Try last distance first. */ for (i = 0; i < (size_t)self->num_last_distances_to_check_; ++i) { const size_t backward = (size_t)distance_cache[i];
diff --git a/enc/hash_longest_match_simd_opt_inc.h b/enc/hash_longest_match_simd_opt_inc.h new file mode 100644 index 0000000..6c54fae --- /dev/null +++ b/enc/hash_longest_match_simd_opt_inc.h
@@ -0,0 +1,284 @@ +/* NOLINT(build/header_guard) */ +/* Copyright 2010 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 */ +/* A (forgetful) hash table to the data seen by the compressor, to + help create backward references to previous data. + This is a hash map of fixed size (bucket_size_) to a ring buffer of + fixed size (block_size_). The ring buffer contains the last block_size_ + index positions of the given hash key in the compressed data. */ +#define HashLongestMatch HASHER() +#define TAG_HASH_BITS 8 +#define TAG_HASH_MASK ((1 << TAG_HASH_BITS) - 1) +static BROTLI_INLINE size_t FN(HashTypeLength)(void) { return 4; } +static BROTLI_INLINE size_t FN(StoreLookahead)(void) { return 4; } +/* HashBytes is the function that chooses the bucket to place the address in. */ +static uint32_t FN(HashBytes)( + const uint8_t* BROTLI_RESTRICT data, const int shift) { + uint32_t h = BROTLI_UNALIGNED_LOAD32LE(data) * kHashMul32; + /* The higher bits contain more mixture from the multiplication, + so we take our results from there. */ + return (uint32_t)(h >> shift); +} +typedef struct HashLongestMatch { + /* Number of hash buckets. */ + size_t bucket_size_; + /* Only block_size_ newest backward references are kept, + and the older are forgotten. */ + size_t block_size_; + /* Left-shift for computing hash bucket index from hash value. */ + int hash_shift_; + /* Mask for accessing entries in a block (in a ring-buffer manner). */ + uint32_t block_mask_; + int block_bits_; + int num_last_distances_to_check_; + /* Shortcuts. */ + HasherCommon* common_; + /* --- Dynamic size members --- */ + /* Number of entries in a particular bucket. */ + uint16_t* num_; /* uint16_t[bucket_size]; */ + uint8_t* tags_; + /* Buckets containing block_size_ of backward references. */ + uint32_t* buckets_; /* uint32_t[bucket_size * block_size]; */ +} HashLongestMatch; +static void FN(Initialize)( + HasherCommon* common, HashLongestMatch* BROTLI_RESTRICT self, + const BrotliEncoderParams* params) { + self->common_ = common; + BROTLI_UNUSED(params); + self->hash_shift_ = 32 - common->params.bucket_bits - TAG_HASH_BITS; + self->bucket_size_ = (size_t)1 << common->params.bucket_bits; + self->block_size_ = (size_t)1 << common->params.block_bits; + self->block_mask_ = (uint32_t)(self->block_size_ - 1); + self->num_ = (uint16_t*)common->extra[0]; + self->tags_ = (uint8_t*)common->extra[1]; + self->buckets_ = (uint32_t*)common->extra[2]; + self->block_bits_ = common->params.block_bits; + self->num_last_distances_to_check_ = + common->params.num_last_distances_to_check; +} +static void FN(Prepare)( + HashLongestMatch* BROTLI_RESTRICT self, BROTLI_BOOL one_shot, + size_t input_size, const uint8_t* BROTLI_RESTRICT data) { + uint16_t* BROTLI_RESTRICT num = self->num_; + /* Partial preparation is 100 times slower (per socket). */ + size_t partial_prepare_threshold = self->bucket_size_ >> 6; + if (one_shot && input_size <= partial_prepare_threshold) { + size_t i; + for (i = 0; i < input_size; ++i) { + const uint32_t hash = FN(HashBytes)(&data[i], self->hash_shift_); + const uint32_t key = hash >> TAG_HASH_BITS; + num[key] = 65535; + } + } else { + /* Set all the bytes of num to 255, which makes each uint16_t 65535. */ + memset(num, 255, self->bucket_size_ * sizeof(num[0])); + } +} +static BROTLI_INLINE void FN(HashMemAllocInBytes)( + const BrotliEncoderParams* params, BROTLI_BOOL one_shot, + size_t input_size, size_t* alloc_size) { + size_t bucket_size = (size_t)1 << params->hasher.bucket_bits; + size_t block_size = (size_t)1 << params->hasher.block_bits; + BROTLI_UNUSED(one_shot); + BROTLI_UNUSED(input_size); + alloc_size[0] = sizeof(uint16_t) * bucket_size; + alloc_size[1] = sizeof(uint8_t) * bucket_size * block_size; + alloc_size[2] = sizeof(uint32_t) * bucket_size * block_size; +} +/* Look at 4 bytes at &data[ix & mask]. + Compute a hash from these, and store the value of ix at that position. */ +static BROTLI_INLINE void FN(Store)( + HashLongestMatch* BROTLI_RESTRICT self, const uint8_t* BROTLI_RESTRICT data, + const size_t mask, const size_t ix) { + uint16_t* BROTLI_RESTRICT num = self->num_; + uint8_t* BROTLI_RESTRICT tags = self->tags_; + uint32_t* BROTLI_RESTRICT buckets = self->buckets_; + const size_t hash = FN(HashBytes)(&data[ix & mask], self->hash_shift_); + const size_t key = hash >> TAG_HASH_BITS; + const uint8_t tag = hash & TAG_HASH_MASK; + const size_t minor_ix = num[key] & self->block_mask_; + const size_t offset = minor_ix + (key << self->block_bits_); + --num[key]; + buckets[offset] = (uint32_t)ix; + tags[offset] = tag; +} +static BROTLI_NOINLINE void FN(StoreRange)( + HashLongestMatch* BROTLI_RESTRICT self, const uint8_t* BROTLI_RESTRICT data, + const size_t mask, const size_t ix_start, const size_t ix_end) { + size_t i; + for (i = ix_start; i < ix_end; ++i) { + FN(Store)(self, data, mask, i); + } +} +static BROTLI_INLINE void FN(StitchToPreviousBlock)( + HashLongestMatch* BROTLI_RESTRICT self, + size_t num_bytes, size_t position, const uint8_t* ringbuffer, + size_t ringbuffer_mask) { + if (num_bytes >= FN(HashTypeLength)() - 1 && position >= 3) { + /* Prepare the hashes for three last bytes of the last write. + These could not be calculated before, since they require knowledge + of both the previous and the current block. */ + FN(Store)(self, ringbuffer, ringbuffer_mask, position - 3); + FN(Store)(self, ringbuffer, ringbuffer_mask, position - 2); + FN(Store)(self, ringbuffer, ringbuffer_mask, position - 1); + } +} +static BROTLI_INLINE void FN(PrepareDistanceCache)( + HashLongestMatch* BROTLI_RESTRICT self, + int* BROTLI_RESTRICT distance_cache) { + PrepareDistanceCache(distance_cache, self->num_last_distances_to_check_); +} + +/* Find a longest backward match of &data[cur_ix] up to the length of + max_length and stores the position cur_ix in the hash table. + REQUIRES: FN(PrepareDistanceCache) must be invoked for current distance cache + values; if this method is invoked repeatedly with the same distance + cache values, it is enough to invoke FN(PrepareDistanceCache) once. + Does not look for matches longer than max_length. + Does not look for matches further away than max_backward. + Writes the best match into |out|. + |out|->score is updated only if a better match is found. */ +static BROTLI_INLINE void FN(FindLongestMatch)( + HashLongestMatch* BROTLI_RESTRICT self, + const BrotliEncoderDictionary* dictionary, + const uint8_t* BROTLI_RESTRICT data, const size_t ring_buffer_mask, + const int* BROTLI_RESTRICT distance_cache, const size_t cur_ix, + const size_t max_length, const size_t max_backward, + const size_t dictionary_distance, const size_t max_distance, + HasherSearchResult* BROTLI_RESTRICT out) { + uint16_t* BROTLI_RESTRICT num = self->num_; + uint32_t* BROTLI_RESTRICT buckets = self->buckets_; + uint8_t* BROTLI_RESTRICT tags = self->tags_; + const size_t cur_ix_masked = cur_ix & ring_buffer_mask; + /* Don't accept a short copy from far away. */ + score_t min_score = out->score; + score_t best_score = out->score; + /* If we're still searching the static dictionary, we have to do the full + search to determine if we should check the static dictionary. */ + size_t best_len = + self->common_->dict_num_matches < (self->common_->dict_num_lookups >> 7) + ? out->len + : 0; + size_t i; + /* Precalculate the hash key and prefetch the bucket. */ + const uint32_t hash = + FN(HashBytes)(&data[cur_ix_masked], self->hash_shift_); + const uint32_t key = hash >> TAG_HASH_BITS; + uint32_t* BROTLI_RESTRICT bucket = &buckets[key << self->block_bits_]; + uint8_t* BROTLI_RESTRICT tag_bucket = &tags[key << self->block_bits_]; + PREFETCH_L1(bucket); + PREFETCH_L1(tag_bucket); + if (self->block_bits_ > 4) PREFETCH_L1(bucket + 16); + out->len = 0; + out->len_code_delta = 0; + + if (best_len < 1) { + best_len = 1; + } + /* Try last distance first. */ + for (i = 0; i < (size_t)self->num_last_distances_to_check_; ++i) { + const size_t backward = (size_t)distance_cache[i]; + size_t prev_ix = (size_t)(cur_ix - backward); + if (prev_ix >= cur_ix) { + continue; + } + if (BROTLI_PREDICT_FALSE(backward > max_backward)) { + continue; + } + prev_ix &= ring_buffer_mask; + + if (BrotliUnalignedRead16(&data[cur_ix_masked + best_len - 1]) != + BrotliUnalignedRead16(&data[prev_ix + best_len - 1])) { + continue; + } + { + const size_t len = FindMatchLengthWithLimit(&data[prev_ix], + &data[cur_ix_masked], + max_length); + if (len >= 3 || (len == 2 && i < 2)) { + /* Comparing for >= 2 does not change the semantics, but just saves for + a few unnecessary binary logarithms in backward reference score, + since we are not interested in such short matches. */ + score_t score = BackwardReferenceScoreUsingLastDistance(len); + if (best_score < score) { + if (i != 0) score -= BackwardReferencePenaltyUsingLastDistance(i); + if (best_score < score) { + best_score = score; + best_len = len; + out->len = best_len; + out->distance = backward; + out->score = best_score; + } + } + } + } + } + /* we require matches of len >4, so increase best_len to 3, so we can compare + * 4 bytes all the time. */ + if (best_len < 3) { + best_len = 3; + } + { + const uint8_t tag = hash & TAG_HASH_MASK; + const size_t head = (num[key] + 1) & self->block_mask_; + uint64_t matches = + GetMatchingTagMask(self->block_size_ / 16, tag, tag_bucket, head); + /* Mask off any matches from uninitialized tags. */ + uint16_t n = 65535 - num[key]; + uint64_t block_has_unused_slots = self->block_size_ > n; + uint64_t mask = (block_has_unused_slots << (n & (64 - 1))) - 1; + matches &= mask; + for (; matches > 0; matches &= (matches - 1)) { + const size_t rb_index = + (head + (size_t)BROTLI_TZCNT64(matches)) & self->block_mask_; + size_t prev_ix = bucket[rb_index]; + const size_t backward = cur_ix - prev_ix; + if (BROTLI_PREDICT_FALSE(backward > max_backward)) { + break; + } + prev_ix &= ring_buffer_mask; + if (/* compare 4 bytes ending at best_len + 1 */ + BrotliUnalignedRead32(&data[cur_ix_masked + best_len - 3]) != + BrotliUnalignedRead32(&data[prev_ix + best_len - 3])) { + continue; + } + { + const size_t len = FindMatchLengthWithLimit(&data[prev_ix], + &data[cur_ix_masked], + max_length); + if (len >= 4) { + /* Comparing for >= 3 does not change the semantics, but just saves + for a few unnecessary binary logarithms in backward reference + score, since we are not interested in such short matches. */ + score_t score = BackwardReferenceScore(len, backward); + if (best_score < score) { + best_score = score; + best_len = len; + out->len = best_len; + out->distance = backward; + out->score = best_score; + } + } + } + } + bucket[num[key] & self->block_mask_] = (uint32_t)cur_ix; + tag_bucket[num[key] & self->block_mask_] = tag; + --num[key]; + } + if (min_score == out->score) { + SearchInStaticDictionary(dictionary, + self->common_, &data[cur_ix_masked], max_length, dictionary_distance, + max_distance, out, BROTLI_FALSE); + } +} +/* `StoreRange` is deliberately not inlined, so it is not eligible for the + `BROTLI_INLINE` treatment that keeps unused hasher methods quiet; reference + it explicitly for the translation units that do not call it. */ +BROTLI_UNUSED_FUNCTION void FN(SuppressUnusedFunctions)(void) { + BROTLI_UNUSED(&FN(SuppressUnusedFunctions)); + BROTLI_UNUSED(&FN(StoreRange)); +} +#undef HashLongestMatch
diff --git a/enc/metablock.c b/enc/metablock.c index dfe8120..2204f17 100644 --- a/enc/metablock.c +++ b/enc/metablock.c
@@ -9,23 +9,171 @@ #include "metablock.h" -#include "../common/constants.h" -#include "../common/context.h" -#include "../common/platform.h" #include "bit_cost.h" #include "block_splitter.h" #include "cluster.h" #include "command.h" #include "entropy_encode.h" +#include "hash.h" #include "histogram.h" #include "memory.h" #include "params.h" #include "prefix.h" +#include "../common/constants.h" +#include "../common/context.h" +#include "../common/platform.h" #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif +static void ForceBase64LiteralSplits(MemoryManager* m, BlockSplit* split, + const Base64Region* base64_regions, + size_t num_base64_regions, + size_t last_flush_pos, + MetaBlockSplit* mb) { + size_t r; + /* Each base64 region can split an existing block into at most 3 blocks + (adding 2 new blocks). In the worst case, every base64 region splits + a different block, increasing the number of blocks by 2 * + num_base64_regions. We add 1 extra block as a safety margin. */ + size_t new_alloc_size = split->num_blocks + num_base64_regions * 2 + 1; + uint8_t* new_types = BROTLI_ALLOC(m, uint8_t, new_alloc_size); + uint32_t* new_lengths = BROTLI_ALLOC(m, uint32_t, new_alloc_size); + size_t new_num_blocks = 0; + size_t curr_offset = 0; + size_t block_idx = 0; + + if (BROTLI_IS_OOM(m) || BROTLI_IS_NULL(new_types) || + BROTLI_IS_NULL(new_lengths)) { + return; + } + + memset(mb->literal_is_base64, 0, sizeof(mb->literal_is_base64)); + uint8_t base64_type_id = (uint8_t)split->num_types; + mb->literal_is_base64[base64_type_id >> 3] |= + (uint8_t)(1u << (base64_type_id & 7)); + + for (block_idx = 0; block_idx < split->num_blocks; ++block_idx) { + uint32_t block_len = split->lengths[block_idx]; + uint8_t block_type = split->types[block_idx]; + size_t block_start = curr_offset; + size_t block_end = curr_offset + block_len; + + BROTLI_BOOL intersected = BROTLI_FALSE; + for (r = 0; r < num_base64_regions; ++r) { + size_t b64_start = base64_regions[r].start_literal_pos - last_flush_pos; + size_t b64_end = b64_start + base64_regions[r].length; + + if (b64_start < block_end && b64_end > block_start) { + if (b64_start > block_start) { + new_types[new_num_blocks] = block_type; + new_lengths[new_num_blocks] = (uint32_t)(b64_start - block_start); + new_num_blocks++; + } + size_t intersect_start = BROTLI_MAX(size_t, block_start, b64_start); + size_t intersect_end = BROTLI_MIN(size_t, block_end, b64_end); + new_types[new_num_blocks] = base64_type_id; + new_lengths[new_num_blocks] = + (uint32_t)(intersect_end - intersect_start); + new_num_blocks++; + + if (b64_end < block_end) { + block_start = b64_end; + block_len = (uint32_t)(block_end - b64_end); + continue; + } else { + block_len = 0; + intersected = BROTLI_TRUE; + break; + } + } + } + + if (!intersected && block_len > 0) { + new_types[new_num_blocks] = block_type; + new_lengths[new_num_blocks] = block_len; + new_num_blocks++; + } + curr_offset = block_end; + } + + BROTLI_FREE(m, split->types); + BROTLI_FREE(m, split->lengths); + split->types = new_types; + split->lengths = new_lengths; + split->num_blocks = new_num_blocks; + split->num_types++; +} + +static void AppendBase64Histogram(MemoryManager* m, MetaBlockSplit* mb) { + size_t base64_type_id = mb->literal_split.num_types - 1; + size_t b64_histo_id = mb->literal_histograms_size; + size_t new_context_map_size = + mb->literal_split.num_types << BROTLI_LITERAL_CONTEXT_BITS; + HistogramLiteral* new_histos = + BROTLI_ALLOC(m, HistogramLiteral, mb->literal_histograms_size + 1); + uint32_t* new_context_map = + BROTLI_ALLOC(m, uint32_t, new_context_map_size); + size_t i; + if (BROTLI_IS_OOM(m) || BROTLI_IS_NULL(new_histos) || + BROTLI_IS_NULL(new_context_map)) { + BROTLI_FREE(m, new_histos); + BROTLI_FREE(m, new_context_map); + return; + } + memcpy(new_histos, mb->literal_histograms, + mb->literal_histograms_size * sizeof(HistogramLiteral)); + BROTLI_FREE(m, mb->literal_histograms); + mb->literal_histograms = new_histos; + HistogramClearLiteral(&mb->literal_histograms[b64_histo_id]); + mb->literal_histograms_size++; + + if (mb->literal_context_map != NULL) { + memcpy(new_context_map, mb->literal_context_map, + mb->literal_context_map_size * sizeof(uint32_t)); + BROTLI_FREE(m, mb->literal_context_map); + } else { + /* If context map was NULL (num_contexts == 1), initialize preceding types */ + size_t t; + for (t = 0; t < base64_type_id; ++t) { + for (i = 0; i < 64; ++i) { + new_context_map[(t << 6) + i] = (uint32_t)t; + } + } + } + mb->literal_context_map = new_context_map; + mb->literal_context_map_size = new_context_map_size; + for (i = 0; i < 64; ++i) { + mb->literal_context_map[(base64_type_id << 6) + i] = (uint32_t)b64_histo_id; + } +} + +static void MapBase64RegionsToLiteralPositions( + const Command* commands, size_t num_commands, size_t last_flush_pos, + const Base64Region* in_regions, size_t num_in_regions, + Base64Region* out_regions) { + size_t r; + for (r = 0; r < num_in_regions; ++r) { + size_t target_ring_pos = in_regions[r].start_literal_pos; + size_t curr_ring_pos = last_flush_pos; + size_t curr_lit_pos = 0; + size_t i; + for (i = 0; i < num_commands; ++i) { + const Command cmd = commands[i]; + if (curr_ring_pos + cmd.insert_len_ >= target_ring_pos) { + size_t offset = target_ring_pos > curr_ring_pos ? target_ring_pos - curr_ring_pos : 0; + curr_lit_pos += offset; + break; + } + curr_lit_pos += cmd.insert_len_; + curr_ring_pos += cmd.insert_len_ + CommandCopyLen(&cmd); + } + out_regions[r].start_literal_pos = curr_lit_pos; + out_regions[r].length = in_regions[r].length; + } +} + void BrotliInitDistanceParams(BrotliDistanceParams* dist_params, uint32_t npostfix, uint32_t ndirect, BROTLI_BOOL large_window) { uint32_t alphabet_size_max; @@ -123,16 +271,13 @@ return BROTLI_TRUE; } -void BrotliBuildMetaBlock(MemoryManager* m, - const uint8_t* ringbuffer, - const size_t pos, - const size_t mask, - BrotliEncoderParams* params, - uint8_t prev_byte, - uint8_t prev_byte2, - Command* cmds, - size_t num_commands, - ContextType literal_context_mode, +void BrotliBuildMetaBlock(MemoryManager* m, const uint8_t* ringbuffer, + const size_t pos, const size_t mask, + const Base64Region* base64_regions, + size_t num_base64_regions, + BrotliEncoderParams* params, uint8_t prev_byte, + uint8_t prev_byte2, Command* cmds, + size_t num_commands, ContextType literal_context_mode, MetaBlockSplit* mb) { /* Histogram ids need to fit in one byte. */ static const size_t kMaxNumberOfHistograms = 256; @@ -263,6 +408,21 @@ } } + if (num_base64_regions > 0 && mb->literal_split.num_types < 256 && + mb->literal_histograms_size < 256) { + Base64Region local_b64[256]; + size_t safe_num_b64 = BROTLI_MIN(size_t, num_base64_regions, 256); + MapBase64RegionsToLiteralPositions(cmds, num_commands, pos, + base64_regions, safe_num_b64, + local_b64); + ForceBase64LiteralSplits(m, &mb->literal_split, local_b64, + safe_num_b64, 0, mb); + if (!BROTLI_IS_OOM(m)) { + AppendBase64Histogram(m, mb); + } + if (BROTLI_IS_OOM(m)) return; + } + BROTLI_DCHECK(mb->distance_context_map == 0); mb->distance_context_map_size = mb->distance_split.num_types << BROTLI_DISTANCE_CONTEXT_BITS; @@ -547,10 +707,12 @@ static BROTLI_INLINE void BrotliBuildMetaBlockGreedyInternal( MemoryManager* m, GreedyMetablockArena* arena, const uint8_t* ringbuffer, - size_t pos, size_t mask, uint8_t prev_byte, uint8_t prev_byte2, + size_t pos, size_t mask, const Base64Region* base64_regions, + size_t num_base64_regions, uint8_t prev_byte, uint8_t prev_byte2, ContextLut literal_context_lut, const size_t num_contexts, const uint32_t* static_context_map, const Command* commands, size_t n_commands, MetaBlockSplit* mb) { + const size_t start_pos = pos; size_t num_literals = 0; size_t i; for (i = 0; i < n_commands; ++i) { @@ -614,6 +776,36 @@ &arena->lit_blocks.ctx, m, /* is_final = */ BROTLI_TRUE); if (BROTLI_IS_OOM(m)) return; } + + if (num_base64_regions > 0 && mb->literal_split.num_types < 256 && + mb->literal_histograms_size + num_contexts <= 256) { + Base64Region local_b64[256]; + size_t safe_num_b64 = BROTLI_MIN(size_t, num_base64_regions, 256); + MapBase64RegionsToLiteralPositions(commands, n_commands, start_pos, + base64_regions, safe_num_b64, + local_b64); + ForceBase64LiteralSplits(m, &mb->literal_split, local_b64, + safe_num_b64, 0, mb); + if (BROTLI_IS_OOM(m)) return; + { + size_t num_b64_histos = num_contexts; + size_t b64_histo_id = mb->literal_histograms_size; + HistogramLiteral* new_histos = BROTLI_ALLOC( + m, HistogramLiteral, mb->literal_histograms_size + num_b64_histos); + if (BROTLI_IS_OOM(m) || BROTLI_IS_NULL(new_histos)) { + return; + } + memcpy(new_histos, mb->literal_histograms, + mb->literal_histograms_size * sizeof(HistogramLiteral)); + BROTLI_FREE(m, mb->literal_histograms); + mb->literal_histograms = new_histos; + for (i = 0; i < num_b64_histos; ++i) { + HistogramClearLiteral(&mb->literal_histograms[b64_histo_id + i]); + } + mb->literal_histograms_size += num_b64_histos; + } + } + BlockSplitterFinishBlockCommand( &arena->cmd_blocks, /* is_final = */ BROTLI_TRUE); BlockSplitterFinishBlockDistance( @@ -624,26 +816,22 @@ } } -void BrotliBuildMetaBlockGreedy(MemoryManager* m, - const uint8_t* ringbuffer, - size_t pos, - size_t mask, - uint8_t prev_byte, - uint8_t prev_byte2, - ContextLut literal_context_lut, - size_t num_contexts, - const uint32_t* static_context_map, - const Command* commands, - size_t n_commands, - MetaBlockSplit* mb) { +void BrotliBuildMetaBlockGreedy( + MemoryManager* m, const uint8_t* ringbuffer, size_t pos, size_t mask, + const Base64Region* base64_regions, size_t num_base64_regions, + uint8_t prev_byte, uint8_t prev_byte2, ContextLut literal_context_lut, + size_t num_contexts, const uint32_t* static_context_map, + const Command* commands, size_t n_commands, MetaBlockSplit* mb) { GreedyMetablockArena* arena = BROTLI_ALLOC(m, GreedyMetablockArena, 1); if (BROTLI_IS_OOM(m) || BROTLI_IS_NULL(arena)) return; if (num_contexts == 1) { - BrotliBuildMetaBlockGreedyInternal(m, arena, ringbuffer, pos, mask, + BrotliBuildMetaBlockGreedyInternal( + m, arena, ringbuffer, pos, mask, base64_regions, num_base64_regions, prev_byte, prev_byte2, literal_context_lut, 1, NULL, commands, n_commands, mb); } else { - BrotliBuildMetaBlockGreedyInternal(m, arena, ringbuffer, pos, mask, + BrotliBuildMetaBlockGreedyInternal( + m, arena, ringbuffer, pos, mask, base64_regions, num_base64_regions, prev_byte, prev_byte2, literal_context_lut, num_contexts, static_context_map, commands, n_commands, mb); }
diff --git a/enc/metablock.h b/enc/metablock.h index e7d1052..5e2b456 100644 --- a/enc/metablock.h +++ b/enc/metablock.h
@@ -10,13 +10,14 @@ #ifndef BROTLI_ENC_METABLOCK_H_ #define BROTLI_ENC_METABLOCK_H_ -#include "../common/context.h" -#include "../common/platform.h" #include "block_splitter.h" #include "command.h" +#include "hash.h" #include "histogram.h" #include "memory.h" #include "params.h" +#include "../common/context.h" +#include "../common/platform.h" #if defined(__cplusplus) || defined(c_plusplus) extern "C" { @@ -38,6 +39,7 @@ size_t command_histograms_size; HistogramDistance* distance_histograms; size_t distance_histograms_size; + uint8_t literal_is_base64[32]; } MetaBlockSplit; static BROTLI_INLINE void InitMetaBlockSplit(MetaBlockSplit* mb) { @@ -54,6 +56,7 @@ mb->command_histograms_size = 0; mb->distance_histograms = 0; mb->distance_histograms_size = 0; + memset(mb->literal_is_base64, 0, sizeof(mb->literal_is_base64)); } static BROTLI_INLINE void DestroyMetaBlockSplit( @@ -72,23 +75,19 @@ The distance parameters are dynamically selected based on the commands which get recomputed under the new distance parameters. The new distance parameters are stored into *params. */ -BROTLI_INTERNAL void BrotliBuildMetaBlock(MemoryManager* m, - const uint8_t* ringbuffer, - const size_t pos, - const size_t mask, - BrotliEncoderParams* params, - uint8_t prev_byte, - uint8_t prev_byte2, - Command* cmds, - size_t num_commands, - ContextType literal_context_mode, - MetaBlockSplit* mb); +BROTLI_INTERNAL void BrotliBuildMetaBlock( + MemoryManager* m, const uint8_t* ringbuffer, const size_t pos, + const size_t mask, const Base64Region* base64_regions, + size_t num_base64_regions, BrotliEncoderParams* params, uint8_t prev_byte, + uint8_t prev_byte2, Command* cmds, size_t num_commands, + ContextType literal_context_mode, MetaBlockSplit* mb); /* Uses a fast greedy block splitter that tries to merge current block with the last or the second last block and uses a static context clustering which is the same for all block types. */ BROTLI_INTERNAL void BrotliBuildMetaBlockGreedy( MemoryManager* m, const uint8_t* ringbuffer, size_t pos, size_t mask, + const Base64Region* base64_regions, size_t num_base64_regions, uint8_t prev_byte, uint8_t prev_byte2, ContextLut literal_context_lut, size_t num_contexts, const uint32_t* static_context_map, const Command* commands, size_t n_commands, MetaBlockSplit* mb);
diff --git a/enc/params.h b/enc/params.h index fd1de00..b1659dc 100644 --- a/enc/params.h +++ b/enc/params.h
@@ -41,6 +41,11 @@ BrotliDistanceParams dist; /* TODO(eustas): rename to BrotliShared... */ SharedEncoderDictionary dictionary; + int base64_mode; + size_t max_base64_regions; + size_t min_base64_region_len; + BrotliEncoderSimdHasher simd_hasher; + BROTLI_BOOL hasher_opt; } BrotliEncoderParams; #endif /* BROTLI_ENC_PARAMS_H_ */
diff --git a/enc/quality.h b/enc/quality.h index 45d67bf..b3d2bed 100644 --- a/enc/quality.h +++ b/enc/quality.h
@@ -128,14 +128,17 @@ - q04: h54 (longest_match_quickly), b20, l7 | for large files - q05: h58 (longest_match_simd ), b14, l4 + - q05: h59 (longest_match_simd_opt), b14, l4 - q05: h68 (longest_match64_simd ), b15, l5 | for large files - q05: h40 (forgetful_chain ), b15, l4 | for small window - q06: h58 (longest_match_simd ), b14, l4 + - q06: h59 (longest_match_simd_opt), b14, l4 - q06: h68 (longest_match64_simd ), b15, l5 | for large files - q06: h40 (forgetful_chain ), b15, l4 | for small window - q07: h58 (longest_match_simd ), b15, l4 + - q07: h59 (longest_match_simd_opt), b15, l4 - q07: h68 (longest_match64_simd ), b15, l5 | for large files - q07: h41 (forgetful_chain ), b15, l4 | for small window @@ -153,6 +156,22 @@ Where "q" is quality, "h" is hasher type, "b" is bucket bits, "l" is source len. */ + +#if defined(BROTLI_MAX_SIMD_QUALITY) +static BROTLI_INLINE BROTLI_BOOL +ShouldUseSimdHasher(const BrotliEncoderParams* params) { + if (params->simd_hasher == BROTLI_SIMD_HASHER_DISABLE) { + return BROTLI_FALSE; + } + int max_quality = BROTLI_MAX_RECOMMENDED_SIMD_QUALITY; + + if (params->simd_hasher == BROTLI_SIMD_HASHER_ENABLE) { + max_quality = BROTLI_MAX_SIMD_QUALITY; + } + return TO_BROTLI_BOOL(params->quality <= max_quality); +} +#endif + static BROTLI_INLINE void ChooseHasher(const BrotliEncoderParams* params, BrotliHasherParams* hparams) { if (params->quality > 9) { @@ -165,7 +184,7 @@ hparams->type = params->quality < 7 ? 40 : params->quality < 9 ? 41 : 42; } else if (params->size_hint >= (1 << 20) && params->lgwin >= 19) { #if defined(BROTLI_MAX_SIMD_QUALITY) - hparams->type = params->quality <= BROTLI_MAX_SIMD_QUALITY ? 68 : 6; + hparams->type = ShouldUseSimdHasher(params) ? 68 : 6; #else hparams->type = 6; #endif @@ -177,7 +196,8 @@ /* TODO(eustas): often previous setting (H6) is faster and denser; consider adding an option to use it. */ #if defined(BROTLI_MAX_SIMD_QUALITY) - hparams->type = params->quality <= BROTLI_MAX_SIMD_QUALITY ? 58 : 5; + hparams->type = + ShouldUseSimdHasher(params) ? (params->hasher_opt ? 59 : 58) : 5; #else hparams->type = 5; #endif
diff --git a/enc/ringbuffer.h b/enc/ringbuffer.h index 5bb75ed..71ba9a5 100644 --- a/enc/ringbuffer.h +++ b/enc/ringbuffer.h
@@ -123,9 +123,11 @@ later when we copy the last two bytes to the first two positions. */ rb->buffer_[rb->size_ - 2] = 0; rb->buffer_[rb->size_ - 1] = 0; - /* Initialize tail; might be touched by "best_len++" optimization when - ring buffer is "full". */ - rb->buffer_[rb->size_] = 241; + /* Mirror the bytes written by the "first write" shortcut (see above) into + the tail. That deliberately avoids initializing the ring buffer tail, + but that requires that we copy the bytes here. */ + memcpy(&rb->buffer_[rb->size_], rb->buffer_, + BROTLI_MIN(size_t, rb->pos_, rb->tail_size_)); } { const size_t masked_pos = rb->pos_ & rb->mask_;
diff --git a/include/brotli/encode.h b/include/brotli/encode.h index ac26ea8..22c57ab 100644 --- a/include/brotli/encode.h +++ b/include/brotli/encode.h
@@ -57,6 +57,33 @@ BROTLI_MODE_FONT = 2 } BrotliEncoderMode; +/** Options for ::BROTLI_PARAM_BASE64_MODE parameter. */ +typedef enum BrotliEncoderBase64Mode { + /** Base64 optimization is disabled. */ + BROTLI_BASE64_MODE_DISABLED = 0, + /** Automatic detection of Base64 zones and direct jump (skipping dictionary + and LZ77 lookups). */ + BROTLI_BASE64_MODE_DETECTION = 1 +} BrotliEncoderBase64Mode; + +#define BROTLI_DEFAULT_BASE64_MODE BROTLI_BASE64_MODE_DISABLED + +#define BROTLI_DEFAULT_MAX_BASE64_REGIONS 16 + +#define BROTLI_DEFAULT_MIN_BASE64_REGION_LEN 2048 + +/** Options for ::BROTLI_PARAM_SIMD_HASHER parameter. */ +typedef enum BrotliEncoderSimdHasher { + /** Use SIMD hasher when recommended for the quality level. */ + BROTLI_SIMD_HASHER_DEFAULT = 0, + /** Use SIMD hasher when supported up to quality 7. */ + BROTLI_SIMD_HASHER_ENABLE = 1, + /** Never use SIMD hasher. */ + BROTLI_SIMD_HASHER_DISABLE = 2 +} BrotliEncoderSimdHasher; + +#define BROTLI_DEFAULT_SIMD_HASHER BROTLI_SIMD_HASHER_DEFAULT + /** Default value for ::BROTLI_PARAM_QUALITY parameter. */ #define BROTLI_DEFAULT_QUALITY 11 /** Default value for ::BROTLI_PARAM_LGWIN parameter. */ @@ -218,7 +245,38 @@ * maximal window size have the same effect. Values greater than 2**30 are not * allowed. */ - BROTLI_PARAM_STREAM_OFFSET = 9 + BROTLI_PARAM_STREAM_OFFSET = 9, + /** + * Base64 encoding mode. Controls how the encoder handles Base64 content. + * Currently supports 0 (disabled) and 1 (automatic detection and skip + * dictionary lookups). + */ + BROTLI_PARAM_BASE64_MODE = 10, + /** + * Maximum number of Base64 regions to detect. + * Default is 16. + */ + BROTLI_PARAM_MAX_BASE64_REGIONS = 11, + /** + * SIMD hasher usage mode. + * + * Controls whether the encoder uses SIMD hashers. + * See ::BrotliEncoderSimdHasher for options. + */ + BROTLI_PARAM_SIMD_HASHER = 12, + /** + * Engage optimized hasher. + * + * When enabled (1), engages H59 instead of H58. + */ + BROTLI_PARAM_HASHER_OPT = 13, + /** + * Minimum length of a Base64 region to trigger detection and literal block + * splitting. Below this threshold, Base64 regions are encoded using standard + * LZ77 and Huffman coding. + * Default is 2048. + */ + BROTLI_PARAM_MIN_BASE64_REGION_LEN = 14 } BrotliEncoderParameter; /**
diff --git a/include/brotli/shared_dictionary.h b/include/brotli/shared_dictionary.h index 2970c2d..fdfdbfc 100644 --- a/include/brotli/shared_dictionary.h +++ b/include/brotli/shared_dictionary.h
@@ -22,6 +22,18 @@ #define SHARED_BROTLI_MAX_COMPOUND_DICTS 15 /** + * Practical limit for RAW dictionary size. + * + * Maximal distance (for non large window mode) is `(2 << 26 + 11) << NPOSTFIX`. + * Usually NPOSTFIX is 0 for regular data (i.e. not WORD/DWORD/QWORD stream). + * In large window mode, distance could be much larger. + * + * Currently, there are no known use cases for larger than 64MiB dictionary in + * regular mode and 2GiB in large window mode. + */ +#define SHARED_BROTLI_MAX_RAW_DICT_SIZE (1u << (23u + sizeof(size_t))) + +/** * Opaque structure that holds shared dictionary data. * * Allocated and initialized with ::BrotliSharedDictionaryCreateInstance.
diff --git a/include/brotli/types.h b/include/brotli/types.h index 2dc61ff..7b5a6f5 100644 --- a/include/brotli/types.h +++ b/include/brotli/types.h
@@ -41,7 +41,7 @@ * evaluated, for example: @code{.cpp} * if (SomeBrotliFunction(encoder, BROTLI_TRUE) && * !OtherBrotliFunction(decoder, BROTLI_FALSE)) { - * bool x = !!YetAnotherBrotliFunction(encoder, TO_BROLTI_BOOL(2 * 2 == 4)); + * bool x = !!YetAnotherBrotliFunction(encoder, TO_BROTLI_BOOL(2 * 2 == 4)); * DoSomething(x); * } * @endcode
diff --git a/tools/brotli.c b/tools/brotli.c index fe088d4..433efd6 100644 --- a/tools/brotli.c +++ b/tools/brotli.c
@@ -50,8 +50,8 @@ #define fopen ms_fopen #define open ms_open -#define chmod(F, P) (0) -#define chown(F, O, G) (0) +#define fchmod(FD, P) (0) +#define fchown(FD, O, G) (0) #if defined(_MSC_VER) && (_MSC_VER >= 1400) #define fseek _fseeki64 @@ -79,6 +79,9 @@ #define HAVE_UTIMENSAT 1 #elif defined(_ATFILE_SOURCE) #define HAVE_UTIMENSAT 1 +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || \ + defined(__DragonFly__) +#define HAVE_UTIMENSAT 1 #else #define HAVE_UTIMENSAT 0 #endif @@ -301,11 +304,6 @@ contain pointers to strings"; NULL and 0-length are not forbidden. */ size_t arg_len = arg ? strlen(arg) : 0; - if (arg_len == 0) { - params->not_input_indices[next_option_index++] = i; - continue; - } - /* Too many options. The expected longest option list is: "-q 0 -w 10 -o f -D d -S b -d -f -k -n -v -K --", i.e. 17 items in total. This check is an additional guard that is never triggered, but provides @@ -315,6 +313,11 @@ return COMMAND_INVALID; } + if (arg_len == 0) { + params->not_input_indices[next_option_index++] = i; + continue; + } + /* Input file entry. */ if (after_dash_dash || arg[0] != '-' || arg_len == 1) { input_count++; @@ -833,16 +836,21 @@ return retval; } -static int CopyTimeStat(const struct stat* statbuf, const char* output_path) { +static int CopyTimeStat(const struct stat* statbuf, const char* output_path, + FILE* fout) { #if HAVE_UTIMENSAT struct timespec times[2]; + int fd = fileno(fout); + (void)output_path; + if (fd < 0) return -1; times[0].tv_sec = statbuf->st_atime; times[0].tv_nsec = ATIME_NSEC(statbuf); times[1].tv_sec = statbuf->st_mtime; times[1].tv_nsec = MTIME_NSEC(statbuf); - return utimensat(AT_FDCWD, output_path, times, AT_SYMLINK_NOFOLLOW); + return futimens(fd, times); #else struct utimbuf times; + (void)fout; times.actime = statbuf->st_atime; times.modtime = statbuf->st_mtime; return utime(output_path, ×); @@ -852,27 +860,33 @@ /* Copy file times and permissions. TODO(eustas): this is a "best effort" implementation; honest cross-platform fully featured implementation is way too hacky; add more hacks by request. */ -static void CopyStat(const char* input_path, const char* output_path) { +static void CopyStat(const char* input_path, const char* output_path, + FILE* fout) { struct stat statbuf; + int fd; int res; - if (input_path == 0 || output_path == 0) { + if (input_path == 0 || output_path == 0 || fout == NULL) { + return; + } + fd = fileno(fout); + if (fd < 0) { return; } if (stat(input_path, &statbuf) != 0) { return; } - res = CopyTimeStat(&statbuf, output_path); - res = chmod(output_path, statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + res = CopyTimeStat(&statbuf, output_path, fout); + res = fchmod(fd, statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); if (res != 0) { fprintf(stderr, "setting access bits failed for [%s]: %s\n", PrintablePath(output_path), strerror(errno)); } - res = chown(output_path, (uid_t)-1, statbuf.st_gid); + res = fchown(fd, (uid_t)-1, statbuf.st_gid); if (res != 0) { fprintf(stderr, "setting group failed for [%s]: %s\n", PrintablePath(output_path), strerror(errno)); } - res = chown(output_path, statbuf.st_uid, (gid_t)-1); + res = fchown(fd, statbuf.st_uid, (gid_t)-1); if (res != 0) { fprintf(stderr, "setting user failed for [%s]: %s\n", PrintablePath(output_path), strerror(errno)); @@ -1028,6 +1042,19 @@ BROTLI_BOOL rm_output) { BROTLI_BOOL is_ok = BROTLI_TRUE; if (!context->test_integrity && context->fout) { + /* Flush before the metadata copy, so a failed write is reported. */ + if (fflush(context->fout) != 0) { + fprintf(stderr, "failed to write output [%s]: %s\n", + PrintablePath(context->current_output_path), strerror(errno)); + is_ok = BROTLI_FALSE; + } + /* Apply metadata via the still-open fd, before close, to close the TOCTOU + window between fclose() and the metadata syscalls. */ + if (is_ok && !rm_output && context->copy_stat && + context->current_output_path) { + CopyStat(context->current_input_path, context->current_output_path, + context->fout); + } if (fclose(context->fout) != 0) { if (is_ok) { fprintf(stderr, "fclose failed [%s]: %s\n", @@ -1038,11 +1065,6 @@ if (rm_output && context->current_output_path) { unlink(context->current_output_path); } - - /* TOCTOU violation, but otherwise it is impossible to set file times. */ - if (!rm_output && is_ok && context->copy_stat) { - CopyStat(context->current_input_path, context->current_output_path); - } } if (context->fin) { @@ -1054,7 +1076,7 @@ is_ok = BROTLI_FALSE; } } - if (rm_input && context->current_input_path) { + if (rm_input && is_ok && context->current_input_path) { unlink(context->current_input_path); } @@ -1293,6 +1315,7 @@ if (context->verbosity > 0) { fprintf(stderr, "reason: comment mismatch\n"); } + return BROTLI_FALSE; } return BROTLI_TRUE; }