/src/libjxl/lib/jxl/dec_ans.h
Line | Count | Source |
1 | | // Copyright (c) the JPEG XL Project Authors. All rights reserved. |
2 | | // |
3 | | // Use of this source code is governed by a BSD-style |
4 | | // license that can be found in the LICENSE file. |
5 | | |
6 | | #ifndef LIB_JXL_DEC_ANS_H_ |
7 | | #define LIB_JXL_DEC_ANS_H_ |
8 | | |
9 | | // Library to decode the ANS population counts from the bit-stream and build a |
10 | | // decoding table from them. |
11 | | |
12 | | #include <jxl/memory_manager.h> |
13 | | #include <jxl/types.h> |
14 | | |
15 | | #include <algorithm> |
16 | | #include <cstddef> |
17 | | #include <cstdint> |
18 | | #include <cstring> |
19 | | #include <vector> |
20 | | |
21 | | #include "lib/jxl/ans_common.h" |
22 | | #include "lib/jxl/ans_params.h" |
23 | | #include "lib/jxl/base/bits.h" |
24 | | #include "lib/jxl/base/compiler_specific.h" |
25 | | #include "lib/jxl/base/status.h" |
26 | | #include "lib/jxl/dec_bit_reader.h" |
27 | | #include "lib/jxl/dec_huffman.h" |
28 | | #include "lib/jxl/field_encodings.h" |
29 | | #include "lib/jxl/memory_manager_internal.h" |
30 | | |
31 | | namespace jxl { |
32 | | |
33 | | class ANSSymbolReader; |
34 | | |
35 | | // Experiments show that best performance is typically achieved for a |
36 | | // split-exponent of 3 or 4. Trend seems to be that '4' is better |
37 | | // for large-ish pictures, and '3' better for rather small-ish pictures. |
38 | | // This is plausible - the more special symbols we have, the better |
39 | | // statistics we need to get a benefit out of them. |
40 | | |
41 | | // Our hybrid-encoding scheme has dedicated tokens for the smallest |
42 | | // (1 << split_exponents) numbers, and for the rest |
43 | | // encodes (number of bits) + (msb_in_token sub-leading binary digits) + |
44 | | // (lsb_in_token lowest binary digits) in the token, with the remaining bits |
45 | | // then being encoded as data. |
46 | | // |
47 | | // Example with split_exponent = 4, msb_in_token = 2, lsb_in_token = 0. |
48 | | // |
49 | | // Numbers N in [0 .. 15]: |
50 | | // These get represented as (token=N, bits=''). |
51 | | // Numbers N >= 16: |
52 | | // If n is such that 2**n <= N < 2**(n+1), |
53 | | // and m = N - 2**n is the 'mantissa', |
54 | | // these get represented as: |
55 | | // (token=split_token + |
56 | | // ((n - split_exponent) * 4) + |
57 | | // (m >> (n - msb_in_token)), |
58 | | // bits=m & (1 << (n - msb_in_token)) - 1) |
59 | | // Specifically, we would get: |
60 | | // N = 0 - 15: (token=N, nbits=0, bits='') |
61 | | // N = 16 (10000): (token=16, nbits=2, bits='00') |
62 | | // N = 17 (10001): (token=16, nbits=2, bits='01') |
63 | | // N = 20 (10100): (token=17, nbits=2, bits='00') |
64 | | // N = 24 (11000): (token=18, nbits=2, bits='00') |
65 | | // N = 28 (11100): (token=19, nbits=2, bits='00') |
66 | | // N = 32 (100000): (token=20, nbits=3, bits='000') |
67 | | // N = 65535: (token=63, nbits=13, bits='1111111111111') |
68 | | struct HybridUintConfig { |
69 | | uint32_t split_exponent; |
70 | | uint32_t split_token; |
71 | | uint32_t msb_in_token; |
72 | | uint32_t lsb_in_token; |
73 | | JXL_INLINE void Encode(uint32_t value, uint32_t* JXL_RESTRICT token, |
74 | | uint32_t* JXL_RESTRICT nbits, |
75 | 886M | uint32_t* JXL_RESTRICT bits) const { |
76 | 886M | if (value < split_token) { |
77 | 730M | *token = value; |
78 | 730M | *nbits = 0; |
79 | 730M | *bits = 0; |
80 | 730M | } else { |
81 | 155M | uint32_t n = FloorLog2Nonzero(value); |
82 | 155M | uint32_t m = value - (1 << n); |
83 | 155M | *token = split_token + |
84 | 155M | ((n - split_exponent) << (msb_in_token + lsb_in_token)) + |
85 | 155M | ((m >> (n - msb_in_token)) << lsb_in_token) + |
86 | 155M | (m & ((1 << lsb_in_token) - 1)); |
87 | 155M | *nbits = n - msb_in_token - lsb_in_token; |
88 | 155M | *bits = (value >> lsb_in_token) & ((1UL << *nbits) - 1); |
89 | 155M | } |
90 | 886M | } |
91 | | |
92 | 498k | JXL_INLINE uint32_t LsbMask() const { return (1 << lsb_in_token) - 1; } |
93 | | |
94 | | explicit HybridUintConfig(uint32_t split_exponent = 4, |
95 | | uint32_t msb_in_token = 2, |
96 | | uint32_t lsb_in_token = 0) |
97 | 69.0M | : split_exponent(split_exponent), |
98 | 69.0M | split_token(1 << split_exponent), |
99 | 69.0M | msb_in_token(msb_in_token), |
100 | 69.0M | lsb_in_token(lsb_in_token) { |
101 | 69.0M | JXL_DASSERT(split_exponent >= msb_in_token + lsb_in_token); |
102 | 69.0M | } |
103 | | }; |
104 | | |
105 | | struct LZ77Params : public Fields { |
106 | | LZ77Params(); |
107 | | JXL_FIELDS_NAME(LZ77Params) |
108 | | Status VisitFields(Visitor* JXL_RESTRICT visitor) override; |
109 | | bool enabled; |
110 | | |
111 | | // Symbols above min_symbol use a special hybrid uint encoding and |
112 | | // represent a length, to be added to min_length. |
113 | | uint32_t min_symbol; |
114 | | uint32_t min_length; |
115 | | |
116 | | // Not serialized by VisitFields. |
117 | | HybridUintConfig length_uint_config{0, 0, 0}; |
118 | | |
119 | | size_t nonserialized_distance_context; |
120 | | }; |
121 | | |
122 | | static constexpr size_t kWindowSize = 1 << 20; |
123 | | static constexpr size_t kNumSpecialDistances = 120; |
124 | | // Table of special distance codes from WebP lossless. |
125 | | static constexpr int8_t kSpecialDistances[kNumSpecialDistances][2] = { |
126 | | {0, 1}, {1, 0}, {1, 1}, {-1, 1}, {0, 2}, {2, 0}, {1, 2}, {-1, 2}, |
127 | | {2, 1}, {-2, 1}, {2, 2}, {-2, 2}, {0, 3}, {3, 0}, {1, 3}, {-1, 3}, |
128 | | {3, 1}, {-3, 1}, {2, 3}, {-2, 3}, {3, 2}, {-3, 2}, {0, 4}, {4, 0}, |
129 | | {1, 4}, {-1, 4}, {4, 1}, {-4, 1}, {3, 3}, {-3, 3}, {2, 4}, {-2, 4}, |
130 | | {4, 2}, {-4, 2}, {0, 5}, {3, 4}, {-3, 4}, {4, 3}, {-4, 3}, {5, 0}, |
131 | | {1, 5}, {-1, 5}, {5, 1}, {-5, 1}, {2, 5}, {-2, 5}, {5, 2}, {-5, 2}, |
132 | | {4, 4}, {-4, 4}, {3, 5}, {-3, 5}, {5, 3}, {-5, 3}, {0, 6}, {6, 0}, |
133 | | {1, 6}, {-1, 6}, {6, 1}, {-6, 1}, {2, 6}, {-2, 6}, {6, 2}, {-6, 2}, |
134 | | {4, 5}, {-4, 5}, {5, 4}, {-5, 4}, {3, 6}, {-3, 6}, {6, 3}, {-6, 3}, |
135 | | {0, 7}, {7, 0}, {1, 7}, {-1, 7}, {5, 5}, {-5, 5}, {7, 1}, {-7, 1}, |
136 | | {4, 6}, {-4, 6}, {6, 4}, {-6, 4}, {2, 7}, {-2, 7}, {7, 2}, {-7, 2}, |
137 | | {3, 7}, {-3, 7}, {7, 3}, {-7, 3}, {5, 6}, {-5, 6}, {6, 5}, {-6, 5}, |
138 | | {8, 0}, {4, 7}, {-4, 7}, {7, 4}, {-7, 4}, {8, 1}, {8, 2}, {6, 6}, |
139 | | {-6, 6}, {8, 3}, {5, 7}, {-5, 7}, {7, 5}, {-7, 5}, {8, 4}, {6, 7}, |
140 | | {-6, 7}, {7, 6}, {-7, 6}, {8, 5}, {7, 7}, {-7, 7}, {8, 6}, {8, 7}}; |
141 | 620k | static JXL_INLINE int SpecialDistance(size_t index, int multiplier) { |
142 | 620k | int dist = kSpecialDistances[index][0] + |
143 | 620k | static_cast<int>(multiplier) * kSpecialDistances[index][1]; |
144 | 620k | return (dist > 1) ? dist : 1; |
145 | 620k | } Unexecuted instantiation: encode.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_icc_codec.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_ans.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_ans_simd.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_cluster.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_context_map.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_lz77.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_fast_lossless.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_frame.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_modular.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_patch_dictionary.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_debug_image.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_dot_dictionary.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_detect_dots.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_quant_weights.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_modular_simd.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_coeff_order.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_heuristics.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_adaptive_quantization.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_cache.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_group.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_splines.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_chroma_from_luma.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_ac_strategy.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_entropy_coder.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_jpeg_data.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_encoding.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_ma.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_rct.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_transform.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: enc_palette.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: coeff_order.cc:jxl::SpecialDistance(unsigned long, int) dec_ans.cc:jxl::SpecialDistance(unsigned long, int) Line | Count | Source | 141 | 620k | static JXL_INLINE int SpecialDistance(size_t index, int multiplier) { | 142 | 620k | int dist = kSpecialDistances[index][0] + | 143 | 620k | static_cast<int>(multiplier) * kSpecialDistances[index][1]; | 144 | 620k | return (dist > 1) ? dist : 1; | 145 | 620k | } |
Unexecuted instantiation: dec_cache.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: dec_context_map.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: dec_external_image.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: dec_frame.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: dec_group.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: dec_modular.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: dec_noise.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: dec_patch_dictionary.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: decode.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: epf.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: icc_codec.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: dec_ma.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: encoding.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: modular_image.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: transform.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: rct.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: palette.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: quant_weights.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: stage_blending.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: stage_epf.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: stage_write.cc:jxl::SpecialDistance(unsigned long, int) Unexecuted instantiation: splines.cc:jxl::SpecialDistance(unsigned long, int) |
146 | | |
147 | | struct ANSCode { |
148 | | AlignedMemory alias_tables; |
149 | | std::vector<HuffmanDecodingData> huffman_data; |
150 | | std::vector<HybridUintConfig> uint_config; |
151 | | std::vector<int> degenerate_symbols; |
152 | | bool use_prefix_code; |
153 | | uint8_t log_alpha_size; // for ANS. |
154 | | LZ77Params lz77; |
155 | | // Maximum number of bits necessary to represent the result of a |
156 | | // ReadHybridUint call done with this ANSCode. |
157 | | size_t max_num_bits = 0; |
158 | | JxlMemoryManager* memory_manager; |
159 | | void UpdateMaxNumBits(size_t ctx, size_t symbol); |
160 | | }; |
161 | | |
162 | | class ANSSymbolReader { |
163 | | public: |
164 | | // Invalid symbol reader, to be overwritten. |
165 | 91.2k | ANSSymbolReader() = default; |
166 | | static StatusOr<ANSSymbolReader> Create(const ANSCode* code, |
167 | | BitReader* JXL_RESTRICT br, |
168 | | size_t distance_multiplier = 0); |
169 | | |
170 | | JXL_INLINE size_t ReadSymbolANSWithoutRefill(const size_t histo_idx, |
171 | 260M | BitReader* JXL_RESTRICT br) { |
172 | 260M | const uint32_t res = state_ & (ANS_TAB_SIZE - 1u); |
173 | | |
174 | 260M | const AliasTable::Entry* table = |
175 | 260M | &alias_tables_[histo_idx << log_alpha_size_]; |
176 | 260M | const AliasTable::Symbol symbol = |
177 | 260M | AliasTable::Lookup(table, res, log_entry_size_, entry_size_minus_1_); |
178 | 260M | state_ = symbol.freq * (state_ >> ANS_LOG_TAB_SIZE) + symbol.offset; |
179 | | |
180 | 260M | #if JXL_TRUE |
181 | | // Branchless version is about equally fast on SKX. |
182 | 260M | const uint32_t new_state = |
183 | 260M | (state_ << 16u) | static_cast<uint32_t>(br->PeekFixedBits<16>()); |
184 | 260M | const bool normalize = state_ < (1u << 16u); |
185 | 260M | state_ = normalize ? new_state : state_; |
186 | 260M | br->Consume(normalize ? 16 : 0); |
187 | | #else |
188 | | if (JXL_UNLIKELY(state_ < (1u << 16u))) { |
189 | | state_ = (state_ << 16u) | br->PeekFixedBits<16>(); |
190 | | br->Consume(16); |
191 | | } |
192 | | #endif |
193 | 260M | const uint32_t next_res = state_ & (ANS_TAB_SIZE - 1u); |
194 | 260M | AliasTable::Prefetch(table, next_res, log_entry_size_); |
195 | | |
196 | 260M | return symbol.value; |
197 | 260M | } |
198 | | |
199 | | JXL_INLINE size_t ReadSymbolHuffWithoutRefill(const size_t histo_idx, |
200 | 217M | BitReader* JXL_RESTRICT br) { |
201 | 217M | return huffman_data_[histo_idx].ReadSymbol(br); |
202 | 217M | } |
203 | | |
204 | | JXL_INLINE size_t ReadSymbolWithoutRefill(const size_t histo_idx, |
205 | 478M | BitReader* JXL_RESTRICT br) { |
206 | | // TODO(veluca): hoist if in hotter loops. |
207 | 478M | if (JXL_UNLIKELY(use_prefix_code_)) { |
208 | 217M | return ReadSymbolHuffWithoutRefill(histo_idx, br); |
209 | 217M | } |
210 | 260M | return ReadSymbolANSWithoutRefill(histo_idx, br); |
211 | 478M | } |
212 | | |
213 | | JXL_INLINE size_t ReadSymbol(const size_t histo_idx, |
214 | 0 | BitReader* JXL_RESTRICT br) { |
215 | 0 | br->Refill(); |
216 | 0 | return ReadSymbolWithoutRefill(histo_idx, br); |
217 | 0 | } |
218 | | |
219 | | #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
220 | 129k | bool CheckANSFinalState() const { return true; } |
221 | | #else |
222 | | bool CheckANSFinalState() const { return state_ == (ANS_SIGNATURE << 16u); } |
223 | | #endif |
224 | | |
225 | | template <typename BitReader> |
226 | | static JXL_INLINE uint32_t ReadHybridUintConfig( |
227 | 478M | const HybridUintConfig& config, size_t token, BitReader* br) { |
228 | 478M | size_t split_token = config.split_token; |
229 | 478M | size_t msb_in_token = config.msb_in_token; |
230 | 478M | size_t lsb_in_token = config.lsb_in_token; |
231 | 478M | size_t split_exponent = config.split_exponent; |
232 | | // Fast-track version of hybrid integer decoding. |
233 | 478M | if (token < split_token) return token; |
234 | 157M | uint32_t nbits = split_exponent - (msb_in_token + lsb_in_token) + |
235 | 157M | ((token - split_token) >> (msb_in_token + lsb_in_token)); |
236 | | // Max amount of bits for ReadBits is 32 and max valid left shift is 29 |
237 | | // bits. However, for speed no error is propagated here, instead limit the |
238 | | // nbits size. If nbits > 29, the code stream is invalid, but no error is |
239 | | // returned. |
240 | | // Note that in most cases we will emit an error if the histogram allows |
241 | | // representing numbers that would cause invalid shifts, but we need to |
242 | | // keep this check as when LZ77 is enabled it might make sense to have an |
243 | | // histogram that could in principle cause invalid shifts. |
244 | 157M | nbits &= 31u; |
245 | 157M | uint32_t low = token & ((1 << lsb_in_token) - 1); |
246 | 157M | token >>= lsb_in_token; |
247 | 157M | const size_t bits = br->PeekBits(nbits); |
248 | 157M | br->Consume(nbits); |
249 | 157M | size_t ret = (((((1 << msb_in_token) | (token & ((1 << msb_in_token) - 1))) |
250 | 157M | << nbits) | |
251 | 157M | bits) |
252 | 157M | << lsb_in_token) | |
253 | 157M | low; |
254 | | // TODO(eustas): mark BitReader as unhealthy if nbits > 29 or ret does not |
255 | | // fit uint32_t |
256 | 157M | return static_cast<uint32_t>(ret); |
257 | 478M | } |
258 | | |
259 | | // Takes a *clustered* idx. Can only use if HuffRleOnly() is true. |
260 | | JXL_INLINE void ReadHybridUintClusteredHuffRleOnly(size_t ctx, |
261 | | BitReader* JXL_RESTRICT br, |
262 | | uint32_t* value, |
263 | 121k | uint32_t* run) { |
264 | 121k | JXL_DASSERT(IsHuffRleOnly()); |
265 | 121k | br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits |
266 | 121k | size_t token = ReadSymbolHuffWithoutRefill(ctx, br); |
267 | 121k | if (JXL_UNLIKELY(token >= lz77_threshold_)) { |
268 | 22.1k | uint32_t addend = |
269 | 22.1k | ReadHybridUintConfig(lz77_length_uint_, token - lz77_threshold_, br); |
270 | 22.1k | uint32_t result = addend + lz77_min_length_ - 1; |
271 | 22.1k | *run = result; |
272 | 22.1k | if (JXL_UNLIKELY(result < addend)) { // overflow |
273 | 0 | *run = 0; |
274 | 0 | br->MarkUnhealthy(); |
275 | 0 | } |
276 | 22.1k | return; |
277 | 22.1k | } |
278 | 99.5k | *value = ReadHybridUintConfig(configs[ctx], token, br); |
279 | 99.5k | } |
280 | 7.92k | bool IsHuffRleOnly() const { |
281 | 7.92k | if (lz77_window_ == nullptr) return false; |
282 | 7.92k | if (!use_prefix_code_) return false; |
283 | 8.95k | for (size_t i = 0; i < kHuffmanTableBits; i++) { |
284 | 8.20k | if (huffman_data_[lz77_ctx_].table_[i].bits) return false; |
285 | 7.41k | if (huffman_data_[lz77_ctx_].table_[i].value != 1) return false; |
286 | 7.41k | } |
287 | 750 | if (configs[lz77_ctx_].split_token > 1) return false; |
288 | 698 | return true; |
289 | 750 | } |
290 | 2.48M | bool UsesLZ77() { return lz77_window_ != nullptr; } |
291 | | |
292 | | // Takes a *clustered* idx. Inlined, for use in hot paths. |
293 | | template <bool uses_lz77> |
294 | | JXL_INLINE size_t ReadHybridUintClusteredInlined(size_t ctx, |
295 | 547M | BitReader* JXL_RESTRICT br) { |
296 | 547M | if (uses_lz77) { |
297 | 173M | if (JXL_UNLIKELY(num_to_copy_ > 0)) { |
298 | 69.6M | size_t ret = lz77_window_[(copy_pos_++) & kWindowMask]; |
299 | 69.6M | num_to_copy_--; |
300 | 69.6M | lz77_window_[(num_decoded_++) & kWindowMask] = ret; |
301 | 69.6M | return ret; |
302 | 69.6M | } |
303 | 173M | } |
304 | | |
305 | 477M | br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits |
306 | 477M | size_t token = ReadSymbolWithoutRefill(ctx, br); |
307 | 477M | if (uses_lz77) { |
308 | 103M | if (JXL_UNLIKELY(token >= lz77_threshold_)) { |
309 | 172k | num_to_copy_ = ReadHybridUintConfig(lz77_length_uint_, |
310 | 172k | token - lz77_threshold_, br) + |
311 | 172k | lz77_min_length_; |
312 | 172k | br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits |
313 | | // Distance code. |
314 | 172k | size_t d_token = ReadSymbolWithoutRefill(lz77_ctx_, br); |
315 | 172k | size_t distance = ReadHybridUintConfig(configs[lz77_ctx_], d_token, br); |
316 | 172k | if (JXL_LIKELY(distance < num_special_distances_)) { |
317 | 78.9k | distance = special_distances_[distance]; |
318 | 93.6k | } else { |
319 | 93.6k | distance = distance + 1 - num_special_distances_; |
320 | 93.6k | } |
321 | 172k | if (JXL_UNLIKELY(distance > num_decoded_)) { |
322 | 57.5k | distance = num_decoded_; |
323 | 57.5k | } |
324 | 172k | if (JXL_UNLIKELY(distance > kWindowSize)) { |
325 | 28.7k | distance = kWindowSize; |
326 | 28.7k | } |
327 | 172k | copy_pos_ = num_decoded_ - distance; |
328 | 172k | if (JXL_UNLIKELY(distance == 0)) { |
329 | 651 | JXL_DASSERT(lz77_window_ != nullptr); |
330 | | // distance 0 -> num_decoded_ == copy_pos_ == 0 |
331 | 651 | size_t to_fill = std::min<size_t>(num_to_copy_, kWindowSize); |
332 | 651 | memset(lz77_window_, 0, to_fill * sizeof(lz77_window_[0])); |
333 | 651 | } |
334 | 172k | if (JXL_UNLIKELY(num_to_copy_ < lz77_min_length_)) { |
335 | | // num_to_copy_ = ReadHybridUintConfig(...) + lz77_min_length_ above |
336 | | // wrapped (lz77_min_length_ added to a value near SIZE_MAX). Stop |
337 | | // accepting further data so the corruption surfaces at the next |
338 | | // AllReadsWithinBounds() / Close() check instead of letting the |
339 | | // decoder keep producing phantom symbols from the LZ77 window. |
340 | 70 | num_to_copy_ = 0; |
341 | 70 | br->MarkUnhealthy(); |
342 | 70 | return 0; |
343 | 70 | } |
344 | | // the code below is the same as doing this: |
345 | | // return ReadHybridUintClustered<uses_lz77>(ctx, br); |
346 | | // but gcc doesn't like recursive inlining |
347 | | |
348 | 172k | size_t ret = lz77_window_[(copy_pos_++) & kWindowMask]; |
349 | 172k | num_to_copy_--; |
350 | 172k | lz77_window_[(num_decoded_++) & kWindowMask] = ret; |
351 | 172k | return ret; |
352 | 172k | } |
353 | 103M | } |
354 | 477M | size_t ret = ReadHybridUintConfig(configs[ctx], token, br); |
355 | 477M | if (uses_lz77 && lz77_window_) |
356 | 52.3M | lz77_window_[(num_decoded_++) & kWindowMask] = ret; |
357 | 477M | return ret; |
358 | 477M | } unsigned long jxl::ANSSymbolReader::ReadHybridUintClusteredInlined<true>(unsigned long, jxl::BitReader*) Line | Count | Source | 295 | 173M | BitReader* JXL_RESTRICT br) { | 296 | 173M | if (uses_lz77) { | 297 | 173M | if (JXL_UNLIKELY(num_to_copy_ > 0)) { | 298 | 69.6M | size_t ret = lz77_window_[(copy_pos_++) & kWindowMask]; | 299 | 69.6M | num_to_copy_--; | 300 | 69.6M | lz77_window_[(num_decoded_++) & kWindowMask] = ret; | 301 | 69.6M | return ret; | 302 | 69.6M | } | 303 | 173M | } | 304 | | | 305 | 103M | br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits | 306 | 103M | size_t token = ReadSymbolWithoutRefill(ctx, br); | 307 | 103M | if (uses_lz77) { | 308 | 103M | if (JXL_UNLIKELY(token >= lz77_threshold_)) { | 309 | 172k | num_to_copy_ = ReadHybridUintConfig(lz77_length_uint_, | 310 | 172k | token - lz77_threshold_, br) + | 311 | 172k | lz77_min_length_; | 312 | 172k | br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits | 313 | | // Distance code. | 314 | 172k | size_t d_token = ReadSymbolWithoutRefill(lz77_ctx_, br); | 315 | 172k | size_t distance = ReadHybridUintConfig(configs[lz77_ctx_], d_token, br); | 316 | 172k | if (JXL_LIKELY(distance < num_special_distances_)) { | 317 | 78.9k | distance = special_distances_[distance]; | 318 | 93.6k | } else { | 319 | 93.6k | distance = distance + 1 - num_special_distances_; | 320 | 93.6k | } | 321 | 172k | if (JXL_UNLIKELY(distance > num_decoded_)) { | 322 | 57.5k | distance = num_decoded_; | 323 | 57.5k | } | 324 | 172k | if (JXL_UNLIKELY(distance > kWindowSize)) { | 325 | 28.7k | distance = kWindowSize; | 326 | 28.7k | } | 327 | 172k | copy_pos_ = num_decoded_ - distance; | 328 | 172k | if (JXL_UNLIKELY(distance == 0)) { | 329 | 651 | JXL_DASSERT(lz77_window_ != nullptr); | 330 | | // distance 0 -> num_decoded_ == copy_pos_ == 0 | 331 | 651 | size_t to_fill = std::min<size_t>(num_to_copy_, kWindowSize); | 332 | 651 | memset(lz77_window_, 0, to_fill * sizeof(lz77_window_[0])); | 333 | 651 | } | 334 | 172k | if (JXL_UNLIKELY(num_to_copy_ < lz77_min_length_)) { | 335 | | // num_to_copy_ = ReadHybridUintConfig(...) + lz77_min_length_ above | 336 | | // wrapped (lz77_min_length_ added to a value near SIZE_MAX). Stop | 337 | | // accepting further data so the corruption surfaces at the next | 338 | | // AllReadsWithinBounds() / Close() check instead of letting the | 339 | | // decoder keep producing phantom symbols from the LZ77 window. | 340 | 70 | num_to_copy_ = 0; | 341 | 70 | br->MarkUnhealthy(); | 342 | 70 | return 0; | 343 | 70 | } | 344 | | // the code below is the same as doing this: | 345 | | // return ReadHybridUintClustered<uses_lz77>(ctx, br); | 346 | | // but gcc doesn't like recursive inlining | 347 | | | 348 | 172k | size_t ret = lz77_window_[(copy_pos_++) & kWindowMask]; | 349 | 172k | num_to_copy_--; | 350 | 172k | lz77_window_[(num_decoded_++) & kWindowMask] = ret; | 351 | 172k | return ret; | 352 | 172k | } | 353 | 103M | } | 354 | 103M | size_t ret = ReadHybridUintConfig(configs[ctx], token, br); | 355 | 103M | if (uses_lz77 && lz77_window_) | 356 | 52.3M | lz77_window_[(num_decoded_++) & kWindowMask] = ret; | 357 | 103M | return ret; | 358 | 103M | } |
unsigned long jxl::ANSSymbolReader::ReadHybridUintClusteredInlined<false>(unsigned long, jxl::BitReader*) Line | Count | Source | 295 | 374M | BitReader* JXL_RESTRICT br) { | 296 | 374M | if (uses_lz77) { | 297 | 0 | if (JXL_UNLIKELY(num_to_copy_ > 0)) { | 298 | 0 | size_t ret = lz77_window_[(copy_pos_++) & kWindowMask]; | 299 | 0 | num_to_copy_--; | 300 | 0 | lz77_window_[(num_decoded_++) & kWindowMask] = ret; | 301 | 0 | return ret; | 302 | 0 | } | 303 | 0 | } | 304 | | | 305 | 374M | br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits | 306 | 374M | size_t token = ReadSymbolWithoutRefill(ctx, br); | 307 | 374M | if (uses_lz77) { | 308 | 0 | if (JXL_UNLIKELY(token >= lz77_threshold_)) { | 309 | 0 | num_to_copy_ = ReadHybridUintConfig(lz77_length_uint_, | 310 | 0 | token - lz77_threshold_, br) + | 311 | 0 | lz77_min_length_; | 312 | 0 | br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits | 313 | | // Distance code. | 314 | 0 | size_t d_token = ReadSymbolWithoutRefill(lz77_ctx_, br); | 315 | 0 | size_t distance = ReadHybridUintConfig(configs[lz77_ctx_], d_token, br); | 316 | 0 | if (JXL_LIKELY(distance < num_special_distances_)) { | 317 | 0 | distance = special_distances_[distance]; | 318 | 0 | } else { | 319 | 0 | distance = distance + 1 - num_special_distances_; | 320 | 0 | } | 321 | 0 | if (JXL_UNLIKELY(distance > num_decoded_)) { | 322 | 0 | distance = num_decoded_; | 323 | 0 | } | 324 | 0 | if (JXL_UNLIKELY(distance > kWindowSize)) { | 325 | 0 | distance = kWindowSize; | 326 | 0 | } | 327 | 0 | copy_pos_ = num_decoded_ - distance; | 328 | 0 | if (JXL_UNLIKELY(distance == 0)) { | 329 | 0 | JXL_DASSERT(lz77_window_ != nullptr); | 330 | | // distance 0 -> num_decoded_ == copy_pos_ == 0 | 331 | 0 | size_t to_fill = std::min<size_t>(num_to_copy_, kWindowSize); | 332 | 0 | memset(lz77_window_, 0, to_fill * sizeof(lz77_window_[0])); | 333 | 0 | } | 334 | 0 | if (JXL_UNLIKELY(num_to_copy_ < lz77_min_length_)) { | 335 | | // num_to_copy_ = ReadHybridUintConfig(...) + lz77_min_length_ above | 336 | | // wrapped (lz77_min_length_ added to a value near SIZE_MAX). Stop | 337 | | // accepting further data so the corruption surfaces at the next | 338 | | // AllReadsWithinBounds() / Close() check instead of letting the | 339 | | // decoder keep producing phantom symbols from the LZ77 window. | 340 | 0 | num_to_copy_ = 0; | 341 | 0 | br->MarkUnhealthy(); | 342 | 0 | return 0; | 343 | 0 | } | 344 | | // the code below is the same as doing this: | 345 | | // return ReadHybridUintClustered<uses_lz77>(ctx, br); | 346 | | // but gcc doesn't like recursive inlining | 347 | | | 348 | 0 | size_t ret = lz77_window_[(copy_pos_++) & kWindowMask]; | 349 | 0 | num_to_copy_--; | 350 | 0 | lz77_window_[(num_decoded_++) & kWindowMask] = ret; | 351 | 0 | return ret; | 352 | 0 | } | 353 | 0 | } | 354 | 374M | size_t ret = ReadHybridUintConfig(configs[ctx], token, br); | 355 | 374M | if (uses_lz77 && lz77_window_) | 356 | 0 | lz77_window_[(num_decoded_++) & kWindowMask] = ret; | 357 | 374M | return ret; | 358 | 374M | } |
|
359 | | |
360 | | // same but not inlined |
361 | | template <bool uses_lz77> |
362 | 96.0M | size_t ReadHybridUintClustered(size_t ctx, BitReader* JXL_RESTRICT br) { |
363 | 96.0M | return ReadHybridUintClusteredInlined<uses_lz77>(ctx, br); |
364 | 96.0M | } unsigned long jxl::ANSSymbolReader::ReadHybridUintClustered<true>(unsigned long, jxl::BitReader*) Line | Count | Source | 362 | 87.2M | size_t ReadHybridUintClustered(size_t ctx, BitReader* JXL_RESTRICT br) { | 363 | 87.2M | return ReadHybridUintClusteredInlined<uses_lz77>(ctx, br); | 364 | 87.2M | } |
unsigned long jxl::ANSSymbolReader::ReadHybridUintClustered<false>(unsigned long, jxl::BitReader*) Line | Count | Source | 362 | 8.82M | size_t ReadHybridUintClustered(size_t ctx, BitReader* JXL_RESTRICT br) { | 363 | 8.82M | return ReadHybridUintClusteredInlined<uses_lz77>(ctx, br); | 364 | 8.82M | } |
|
365 | | |
366 | | // inlined only in the no-lz77 case |
367 | | template <bool uses_lz77> |
368 | | JXL_INLINE size_t |
369 | 165M | ReadHybridUintClusteredMaybeInlined(size_t ctx, BitReader* JXL_RESTRICT br) { |
370 | 165M | if (uses_lz77) { |
371 | 27.7M | return ReadHybridUintClustered<uses_lz77>(ctx, br); |
372 | 137M | } else { |
373 | 137M | return ReadHybridUintClusteredInlined<uses_lz77>(ctx, br); |
374 | 137M | } |
375 | 165M | } unsigned long jxl::ANSSymbolReader::ReadHybridUintClusteredMaybeInlined<true>(unsigned long, jxl::BitReader*) Line | Count | Source | 369 | 27.7M | ReadHybridUintClusteredMaybeInlined(size_t ctx, BitReader* JXL_RESTRICT br) { | 370 | 27.7M | if (uses_lz77) { | 371 | 27.7M | return ReadHybridUintClustered<uses_lz77>(ctx, br); | 372 | 27.7M | } else { | 373 | 0 | return ReadHybridUintClusteredInlined<uses_lz77>(ctx, br); | 374 | 0 | } | 375 | 27.7M | } |
unsigned long jxl::ANSSymbolReader::ReadHybridUintClusteredMaybeInlined<false>(unsigned long, jxl::BitReader*) Line | Count | Source | 369 | 137M | ReadHybridUintClusteredMaybeInlined(size_t ctx, BitReader* JXL_RESTRICT br) { | 370 | 137M | if (uses_lz77) { | 371 | 0 | return ReadHybridUintClustered<uses_lz77>(ctx, br); | 372 | 137M | } else { | 373 | 137M | return ReadHybridUintClusteredInlined<uses_lz77>(ctx, br); | 374 | 137M | } | 375 | 137M | } |
|
376 | | |
377 | | // inlined, for use in hot paths |
378 | | template <bool uses_lz77> |
379 | | JXL_INLINE size_t |
380 | | ReadHybridUintInlined(size_t ctx, BitReader* JXL_RESTRICT br, |
381 | 22.6M | const std::vector<uint8_t>& context_map) { |
382 | 22.6M | return ReadHybridUintClustered<uses_lz77>(context_map[ctx], br); |
383 | 22.6M | } unsigned long jxl::ANSSymbolReader::ReadHybridUintInlined<true>(unsigned long, jxl::BitReader*, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&) Line | Count | Source | 381 | 18.9M | const std::vector<uint8_t>& context_map) { | 382 | 18.9M | return ReadHybridUintClustered<uses_lz77>(context_map[ctx], br); | 383 | 18.9M | } |
unsigned long jxl::ANSSymbolReader::ReadHybridUintInlined<false>(unsigned long, jxl::BitReader*, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&) Line | Count | Source | 381 | 3.68M | const std::vector<uint8_t>& context_map) { | 382 | 3.68M | return ReadHybridUintClustered<uses_lz77>(context_map[ctx], br); | 383 | 3.68M | } |
|
384 | | |
385 | | // not inlined, for use in non-hot paths |
386 | | size_t ReadHybridUint(size_t ctx, BitReader* JXL_RESTRICT br, |
387 | 39.2M | const std::vector<uint8_t>& context_map) { |
388 | 39.2M | return ReadHybridUintClustered</*uses_lz77=*/true>(context_map[ctx], br); |
389 | 39.2M | } |
390 | | |
391 | | // ctx is a *clustered* context! |
392 | | // This function will modify the ANS state as if `count` symbols have been |
393 | | // decoded. |
394 | 361k | bool IsSingleValueAndAdvance(size_t ctx, uint32_t* value, size_t count) { |
395 | | // TODO(veluca): No optimization for Huffman mode yet. |
396 | 361k | if (use_prefix_code_) return false; |
397 | | // TODO(eustas): Check if we could deal with copy tail as well. |
398 | 207k | if (num_to_copy_ != 0) return false; |
399 | | // TODO(eustas): propagate "degenerate_symbol" to simplify this method. |
400 | 207k | const uint32_t res = state_ & (ANS_TAB_SIZE - 1u); |
401 | 207k | const AliasTable::Entry* table = &alias_tables_[ctx << log_alpha_size_]; |
402 | 207k | AliasTable::Symbol symbol = |
403 | 207k | AliasTable::Lookup(table, res, log_entry_size_, entry_size_minus_1_); |
404 | 207k | if (symbol.freq != ANS_TAB_SIZE) return false; |
405 | 183k | if (configs[ctx].split_token <= symbol.value) return false; |
406 | 165k | if (symbol.value >= lz77_threshold_) return false; |
407 | 165k | *value = symbol.value; |
408 | 165k | if (lz77_window_) { |
409 | 16.2M | for (size_t i = 0; i < count; i++) { |
410 | 16.2M | lz77_window_[(num_decoded_++) & kWindowMask] = symbol.value; |
411 | 16.2M | } |
412 | 6.27k | } |
413 | 165k | return true; |
414 | 165k | } |
415 | | |
416 | | static constexpr size_t kMaxCheckpointInterval = 512; |
417 | | struct Checkpoint { |
418 | | uint32_t state; |
419 | | uint32_t num_to_copy; |
420 | | uint32_t copy_pos; |
421 | | uint32_t num_decoded; |
422 | | uint32_t lz77_window[kMaxCheckpointInterval]; |
423 | | }; |
424 | 16.6k | void Save(Checkpoint* checkpoint) { |
425 | 16.6k | checkpoint->state = state_; |
426 | 16.6k | checkpoint->num_decoded = num_decoded_; |
427 | 16.6k | checkpoint->num_to_copy = num_to_copy_; |
428 | 16.6k | checkpoint->copy_pos = copy_pos_; |
429 | 16.6k | if (lz77_window_) { |
430 | 3.51k | size_t win_start = num_decoded_ & kWindowMask; |
431 | 3.51k | size_t win_end = (num_decoded_ + kMaxCheckpointInterval) & kWindowMask; |
432 | 3.51k | if (win_end > win_start) { |
433 | 3.51k | memcpy(checkpoint->lz77_window, lz77_window_ + win_start, |
434 | 3.51k | (win_end - win_start) * sizeof(*lz77_window_)); |
435 | 3.51k | } else { |
436 | 0 | memcpy(checkpoint->lz77_window, lz77_window_ + win_start, |
437 | 0 | (kWindowSize - win_start) * sizeof(*lz77_window_)); |
438 | 0 | memcpy(checkpoint->lz77_window + (kWindowSize - win_start), |
439 | 0 | lz77_window_, win_end * sizeof(*lz77_window_)); |
440 | 0 | } |
441 | 3.51k | } |
442 | 16.6k | } |
443 | 638 | void Restore(const Checkpoint& checkpoint) { |
444 | 638 | state_ = checkpoint.state; |
445 | 638 | JXL_DASSERT(num_decoded_ <= |
446 | 638 | checkpoint.num_decoded + kMaxCheckpointInterval); |
447 | 638 | num_decoded_ = checkpoint.num_decoded; |
448 | 638 | num_to_copy_ = checkpoint.num_to_copy; |
449 | 638 | copy_pos_ = checkpoint.copy_pos; |
450 | 638 | if (lz77_window_) { |
451 | 294 | size_t win_start = num_decoded_ & kWindowMask; |
452 | 294 | size_t win_end = (num_decoded_ + kMaxCheckpointInterval) & kWindowMask; |
453 | 294 | if (win_end > win_start) { |
454 | 294 | memcpy(lz77_window_ + win_start, checkpoint.lz77_window, |
455 | 294 | (win_end - win_start) * sizeof(*lz77_window_)); |
456 | 294 | } else { |
457 | 0 | memcpy(lz77_window_ + win_start, checkpoint.lz77_window, |
458 | 0 | (kWindowSize - win_start) * sizeof(*lz77_window_)); |
459 | 0 | memcpy(lz77_window_, checkpoint.lz77_window + (kWindowSize - win_start), |
460 | 0 | win_end * sizeof(*lz77_window_)); |
461 | 0 | } |
462 | 294 | } |
463 | 638 | } |
464 | | |
465 | | private: |
466 | | ANSSymbolReader(const ANSCode* code, BitReader* JXL_RESTRICT br, |
467 | | size_t distance_multiplier, |
468 | | AlignedMemory&& lz77_window_storage); |
469 | | |
470 | | const AliasTable::Entry* JXL_RESTRICT alias_tables_; // not owned |
471 | | const HuffmanDecodingData* huffman_data_; |
472 | | bool use_prefix_code_; |
473 | | uint32_t state_ = ANS_SIGNATURE << 16u; |
474 | | const HybridUintConfig* JXL_RESTRICT configs; |
475 | | uint32_t log_alpha_size_{}; |
476 | | uint32_t log_entry_size_{}; |
477 | | uint32_t entry_size_minus_1_{}; |
478 | | |
479 | | // LZ77 structures and constants. |
480 | | static constexpr size_t kWindowMask = kWindowSize - 1; |
481 | | // a std::vector incurs unacceptable decoding speed loss because of |
482 | | // initialization. |
483 | | AlignedMemory lz77_window_storage_; |
484 | | uint32_t* lz77_window_ = nullptr; |
485 | | uint32_t num_decoded_ = 0; |
486 | | uint32_t num_to_copy_ = 0; |
487 | | uint32_t copy_pos_ = 0; |
488 | | uint32_t lz77_ctx_ = 0; |
489 | | uint32_t lz77_min_length_ = 0; |
490 | | uint32_t lz77_threshold_ = 1 << 20; // bigger than any symbol. |
491 | | HybridUintConfig lz77_length_uint_; |
492 | | uint32_t special_distances_[kNumSpecialDistances]{}; |
493 | | uint32_t num_special_distances_{}; |
494 | | }; |
495 | | |
496 | | Status DecodeHistograms(JxlMemoryManager* memory_manager, BitReader* br, |
497 | | size_t num_contexts, ANSCode* code, |
498 | | std::vector<uint8_t>* context_map, |
499 | | bool disallow_lz77 = false); |
500 | | |
501 | | // Exposed for tests. |
502 | | Status DecodeUintConfigs(size_t log_alpha_size, |
503 | | std::vector<HybridUintConfig>* uint_config, |
504 | | BitReader* br); |
505 | | |
506 | | } // namespace jxl |
507 | | |
508 | | #endif // LIB_JXL_DEC_ANS_H_ |