/src/libjxl/lib/jxl/ans_common.cc
Line | Count | Source (jump to first uncovered line) |
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 | | #include "lib/jxl/ans_common.h" |
7 | | |
8 | | #include <cstddef> |
9 | | #include <cstdint> |
10 | | #include <vector> |
11 | | |
12 | | #include "lib/jxl/ans_params.h" |
13 | | #include "lib/jxl/base/compiler_specific.h" |
14 | | #include "lib/jxl/base/status.h" |
15 | | |
16 | | namespace jxl { |
17 | | |
18 | | // First, all trailing non-occurring symbols are removed from the distribution; |
19 | | // if this leaves the distribution empty, a placeholder symbol with max weight |
20 | | // is added. This ensures that the resulting distribution sums to total table |
21 | | // size. Then, `entry_size` is chosen to be the largest power of two so that |
22 | | // `table_size` = ANS_TAB_SIZE/`entry_size` is at least as big as the |
23 | | // distribution size. |
24 | | // Note that each entry will only ever contain two different symbols, and |
25 | | // consecutive ranges of offsets, which allows us to use a compact |
26 | | // representation. |
27 | | // Each entry is initialized with only the (symbol=i, offset) pairs; then |
28 | | // positions for which the entry overflows (i.e. distribution[i] > entry_size) |
29 | | // or is not full are computed, and put into a stack in increasing order. |
30 | | // Missing symbols in the distribution are padded with 0 (because `table_size` |
31 | | // >= number of symbols). The `cutoff` value for each entry is initialized to |
32 | | // the number of occupied slots in that entry (i.e. `distributions[i]`). While |
33 | | // the overflowing-symbol stack is not empty (which implies that the |
34 | | // underflowing-symbol stack also is not), the top overfull and underfull |
35 | | // positions are popped from the stack; the empty slots in the underfull entry |
36 | | // are then filled with as many slots as needed from the overfull entry; such |
37 | | // slots are placed after the slots in the overfull entry, and `offsets[1]` is |
38 | | // computed accordingly. The formerly underfull entry is thus now neither |
39 | | // underfull nor overfull, and represents exactly two symbols. The overfull |
40 | | // entry might be either overfull or underfull, and is pushed into the |
41 | | // corresponding stack. |
42 | | Status InitAliasTable(std::vector<int32_t> distribution, uint32_t log_range, |
43 | | size_t log_alpha_size, |
44 | 74.1k | AliasTable::Entry* JXL_RESTRICT a) { |
45 | 74.1k | const uint32_t range = 1 << log_range; |
46 | 74.1k | const size_t table_size = 1 << log_alpha_size; |
47 | 74.1k | JXL_ENSURE(table_size <= range); |
48 | 151k | while (!distribution.empty() && distribution.back() == 0) { |
49 | 77.4k | distribution.pop_back(); |
50 | 77.4k | } |
51 | | // Ensure that a valid table is always returned, even for an empty |
52 | | // alphabet. Otherwise, a specially-crafted stream might crash the |
53 | | // decoder. |
54 | 74.1k | if (distribution.empty()) { |
55 | 0 | distribution.emplace_back(range); |
56 | 0 | } |
57 | 74.1k | JXL_ENSURE(distribution.size() <= table_size); |
58 | 74.1k | const uint32_t entry_size = range >> log_alpha_size; // this is exact |
59 | 74.1k | int single_symbol = -1; |
60 | 74.1k | int sum = 0; |
61 | | // Special case for single-symbol distributions, that ensures that the state |
62 | | // does not change when decoding from such a distribution. Note that, since we |
63 | | // hardcode offset0 == 0, it is not straightforward (if at all possible) to |
64 | | // fix the general case to produce this result. |
65 | 1.23M | for (size_t sym = 0; sym < distribution.size(); sym++) { |
66 | 1.16M | int32_t v = distribution[sym]; |
67 | 1.16M | sum += v; |
68 | 1.16M | if (v == ANS_TAB_SIZE) { |
69 | 22.9k | JXL_ENSURE(single_symbol == -1); |
70 | 22.9k | single_symbol = sym; |
71 | 22.9k | } |
72 | 1.16M | } |
73 | 74.1k | JXL_ENSURE(static_cast<uint32_t>(sum) == range); |
74 | 74.1k | if (single_symbol != -1) { |
75 | 22.9k | uint8_t sym = single_symbol; |
76 | 22.9k | JXL_ENSURE(single_symbol == sym); |
77 | 2.19M | for (size_t i = 0; i < table_size; i++) { |
78 | 2.16M | a[i].right_value = sym; |
79 | 2.16M | a[i].cutoff = 0; |
80 | 2.16M | a[i].offsets1 = entry_size * i; |
81 | 2.16M | a[i].freq0 = 0; |
82 | 2.16M | a[i].freq1_xor_freq0 = ANS_TAB_SIZE; |
83 | 2.16M | } |
84 | 22.9k | return true; |
85 | 22.9k | } |
86 | | |
87 | 51.2k | std::vector<uint32_t> underfull_posn; |
88 | 51.2k | std::vector<uint32_t> overfull_posn; |
89 | 51.2k | std::vector<uint32_t> cutoffs(1 << log_alpha_size); |
90 | | // Initialize entries. |
91 | 1.15M | for (size_t i = 0; i < distribution.size(); i++) { |
92 | 1.10M | cutoffs[i] = distribution[i]; |
93 | 1.10M | if (cutoffs[i] > entry_size) { |
94 | 445k | overfull_posn.push_back(i); |
95 | 657k | } else if (cutoffs[i] < entry_size) { |
96 | 536k | underfull_posn.push_back(i); |
97 | 536k | } |
98 | 1.10M | } |
99 | 3.83M | for (uint32_t i = distribution.size(); i < table_size; i++) { |
100 | 3.77M | cutoffs[i] = 0; |
101 | 3.77M | underfull_posn.push_back(i); |
102 | 3.77M | } |
103 | | // Reassign overflow/underflow values. |
104 | 4.55M | while (!overfull_posn.empty()) { |
105 | 4.50M | uint32_t overfull_i = overfull_posn.back(); |
106 | 4.50M | overfull_posn.pop_back(); |
107 | 4.50M | JXL_ENSURE(!underfull_posn.empty()); |
108 | 4.50M | uint32_t underfull_i = underfull_posn.back(); |
109 | 4.50M | underfull_posn.pop_back(); |
110 | 4.50M | uint32_t underfull_by = entry_size - cutoffs[underfull_i]; |
111 | 4.50M | cutoffs[overfull_i] -= underfull_by; |
112 | | // overfull positions have their original symbols |
113 | 4.50M | a[underfull_i].right_value = overfull_i; |
114 | 4.50M | a[underfull_i].offsets1 = cutoffs[overfull_i]; |
115 | | // Slots in the right part of entry underfull_i were taken from the end |
116 | | // of the symbols in entry overfull_i. |
117 | 4.50M | if (cutoffs[overfull_i] < entry_size) { |
118 | 185k | underfull_posn.push_back(overfull_i); |
119 | 4.31M | } else if (cutoffs[overfull_i] > entry_size) { |
120 | 4.05M | overfull_posn.push_back(overfull_i); |
121 | 4.05M | } |
122 | 4.50M | } |
123 | 4.93M | for (uint32_t i = 0; i < table_size; i++) { |
124 | | // cutoffs[i] is properly initialized but the clang-analyzer doesn't infer |
125 | | // it since it is partially initialized across two for-loops. |
126 | | // NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult) |
127 | 4.88M | if (cutoffs[i] == entry_size) { |
128 | 382k | a[i].right_value = i; |
129 | 382k | a[i].offsets1 = 0; |
130 | 382k | a[i].cutoff = 0; |
131 | 4.50M | } else { |
132 | | // Note that, if cutoff is not equal to entry_size, |
133 | | // a[i].offsets1 was initialized with (overfull cutoff) - |
134 | | // (entry_size - a[i].cutoff). Thus, subtracting |
135 | | // a[i].cutoff cannot make it negative. |
136 | 4.50M | a[i].offsets1 -= cutoffs[i]; |
137 | 4.50M | a[i].cutoff = cutoffs[i]; |
138 | 4.50M | } |
139 | 4.88M | const size_t freq0 = i < distribution.size() ? distribution[i] : 0; |
140 | 4.88M | const size_t i1 = a[i].right_value; |
141 | 4.88M | const size_t freq1 = i1 < distribution.size() ? distribution[i1] : 0; |
142 | 4.88M | a[i].freq0 = static_cast<uint16_t>(freq0); |
143 | 4.88M | a[i].freq1_xor_freq0 = static_cast<uint16_t>(freq1 ^ freq0); |
144 | 4.88M | } |
145 | 51.2k | return true; |
146 | 51.2k | } |
147 | | |
148 | | } // namespace jxl |