/src/libjxl/lib/jxl/padded_bytes.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_BASE_PADDED_BYTES_H_ |
7 | | #define LIB_JXL_BASE_PADDED_BYTES_H_ |
8 | | |
9 | | // std::vector replacement with padding to reduce bounds checks in WriteBits |
10 | | |
11 | | #include <jxl/memory_manager.h> |
12 | | |
13 | | #include <algorithm> // max |
14 | | #include <cstddef> |
15 | | #include <cstdint> |
16 | | #include <cstring> // memcpy |
17 | | #include <initializer_list> |
18 | | #include <utility> // swap |
19 | | |
20 | | #include "lib/jxl/base/status.h" |
21 | | #include "lib/jxl/memory_manager_internal.h" |
22 | | |
23 | | namespace jxl { |
24 | | |
25 | | // Provides a subset of the std::vector interface with some differences: |
26 | | // - allows BitWriter to write 64 bits at a time without bounds checking; |
27 | | // - ONLY zero-initializes the first byte (required by BitWriter); |
28 | | // - ensures cache-line alignment. |
29 | | class PaddedBytes { |
30 | | public: |
31 | | // Required for output params. |
32 | | explicit PaddedBytes(JxlMemoryManager* memory_manager) |
33 | 162k | : memory_manager_(memory_manager), size_(0), capacity_(0) {} |
34 | | |
35 | | static StatusOr<PaddedBytes> WithInitialSpace( |
36 | 12.3k | JxlMemoryManager* memory_manager, size_t size) { |
37 | 12.3k | PaddedBytes result(memory_manager); |
38 | 12.3k | JXL_RETURN_IF_ERROR(result.Init(size)); |
39 | 12.3k | return result; |
40 | 12.3k | } |
41 | | |
42 | | // Deleting copy constructor and copy assignment operator to prevent copying |
43 | | PaddedBytes(const PaddedBytes&) = delete; |
44 | | PaddedBytes& operator=(const PaddedBytes&) = delete; |
45 | | |
46 | | // default is not OK - need to set other.size_ to 0! |
47 | | PaddedBytes(PaddedBytes&& other) noexcept |
48 | 73.2k | : memory_manager_(other.memory_manager_), |
49 | 73.2k | size_(other.size_), |
50 | 73.2k | capacity_(other.capacity_), |
51 | 73.2k | data_(std::move(other.data_)) { |
52 | 73.2k | other.size_ = other.capacity_ = 0; |
53 | 73.2k | } |
54 | 34.5k | PaddedBytes& operator=(PaddedBytes&& other) noexcept { |
55 | 34.5k | memory_manager_ = other.memory_manager_; |
56 | 34.5k | size_ = other.size_; |
57 | 34.5k | capacity_ = other.capacity_; |
58 | 34.5k | data_ = std::move(other.data_); |
59 | | |
60 | 34.5k | if (&other != this) { |
61 | 34.5k | other.size_ = other.capacity_ = 0; |
62 | 34.5k | } |
63 | 34.5k | return *this; |
64 | 34.5k | } |
65 | | |
66 | 22.3k | JxlMemoryManager* memory_manager() const { return memory_manager_; } |
67 | | |
68 | 0 | void swap(PaddedBytes& other) noexcept { |
69 | 0 | std::swap(memory_manager_, other.memory_manager_); |
70 | 0 | std::swap(size_, other.size_); |
71 | 0 | std::swap(capacity_, other.capacity_); |
72 | 0 | std::swap(data_, other.data_); |
73 | 0 | } |
74 | | |
75 | | // If current capacity is greater than requested, then no-op. Otherwise |
76 | | // copies existing data to newly allocated "data_". |
77 | | // The new capacity will be at least 1.5 times the old capacity. This ensures |
78 | | // that we avoid quadratic behaviour. |
79 | 887k | Status reserve(size_t capacity) { |
80 | 887k | if (capacity <= capacity_) return true; |
81 | | |
82 | 113k | size_t new_capacity; |
83 | 113k | if (!SafeAdd(capacity_, capacity_ / 2, new_capacity)) { |
84 | 0 | return JXL_FAILURE("PaddedBytes reserve: capacity overflow"); |
85 | 0 | } |
86 | 113k | new_capacity = std::max(capacity, new_capacity); |
87 | | |
88 | 113k | return change_capacity(new_capacity); |
89 | 113k | } |
90 | | |
91 | 2.92k | Status shrink_to_fit() { return change_capacity(size_); } |
92 | | |
93 | | // NOTE: unlike vector, this does not initialize the new data! |
94 | | // However, we guarantee that write_bits can safely append after |
95 | | // the resize, as we zero-initialize the first new byte of data. |
96 | | // If size < capacity(), does not invalidate the memory. |
97 | 872k | Status resize(size_t size) { |
98 | 872k | JXL_RETURN_IF_ERROR(reserve(size)); |
99 | 872k | size_ = size; |
100 | 872k | return true; |
101 | 872k | } |
102 | | |
103 | | // resize(size) plus explicit initialization of the new data with `value`. |
104 | 0 | Status resize(size_t size, uint8_t value) { |
105 | 0 | size_t old_size = size_; |
106 | 0 | JXL_RETURN_IF_ERROR(resize(size)); |
107 | 0 | if (size_ > old_size) { |
108 | 0 | memset(data() + old_size, value, size_ - old_size); |
109 | 0 | } |
110 | 0 | return true; |
111 | 0 | } |
112 | | |
113 | | // Amortized constant complexity due to exponential growth. |
114 | 195k | Status push_back(uint8_t x) { |
115 | 195k | if (size_ == capacity_) { |
116 | 2.47k | JXL_RETURN_IF_ERROR(reserve(capacity_ + 1)); |
117 | 2.47k | } |
118 | | |
119 | 195k | data_.address<uint8_t>()[size_++] = x; |
120 | 195k | return true; |
121 | 195k | } |
122 | | |
123 | 2.32M | size_t size() const { return size_; } |
124 | 0 | size_t capacity() const { return capacity_; } |
125 | | |
126 | 66.6M | uint8_t* data() { return data_.address<uint8_t>(); } |
127 | 487k | const uint8_t* data() const { return data_.address<uint8_t>(); } |
128 | | |
129 | | // std::vector operations implemented in terms of the public interface above. |
130 | | |
131 | 20.2k | void clear() { |
132 | | // Not passing on the Status, because resizing to 0 cannot fail. |
133 | 20.2k | static_cast<void>(resize(0)); |
134 | 20.2k | } |
135 | 540k | bool empty() const { return size() == 0; } |
136 | | |
137 | 0 | Status assign(std::initializer_list<uint8_t> il) { |
138 | 0 | JXL_RETURN_IF_ERROR(resize(il.size())); |
139 | 0 | memcpy(data(), il.begin(), il.size()); |
140 | 0 | return true; |
141 | 0 | } |
142 | | |
143 | 0 | uint8_t* begin() { return data(); } |
144 | 18.6k | const uint8_t* begin() const { return data(); } |
145 | 0 | uint8_t* end() { return begin() + size(); } |
146 | 9.32k | const uint8_t* end() const { return begin() + size(); } |
147 | | |
148 | 66.3M | uint8_t& operator[](const size_t i) { |
149 | 66.3M | BoundsCheck(i); |
150 | 66.3M | return data()[i]; |
151 | 66.3M | } |
152 | 444k | const uint8_t& operator[](const size_t i) const { |
153 | 444k | BoundsCheck(i); |
154 | 444k | return data()[i]; |
155 | 444k | } |
156 | | |
157 | | template <typename T> |
158 | 20.8k | Status append(const T& other) { |
159 | 20.8k | return append( |
160 | 20.8k | reinterpret_cast<const uint8_t*>(other.data()), |
161 | 20.8k | reinterpret_cast<const uint8_t*>(other.data()) + other.size()); |
162 | 20.8k | } jxl::Status jxl::PaddedBytes::append<std::__1::array<unsigned char, 4ul> >(std::__1::array<unsigned char, 4ul> const&) Line | Count | Source | 158 | 20.0k | Status append(const T& other) { | 159 | 20.0k | return append( | 160 | 20.0k | reinterpret_cast<const uint8_t*>(other.data()), | 161 | 20.0k | reinterpret_cast<const uint8_t*>(other.data()) + other.size()); | 162 | 20.0k | } |
jxl::Status jxl::PaddedBytes::append<std::__1::array<unsigned char, 128ul> >(std::__1::array<unsigned char, 128ul> const&) Line | Count | Source | 158 | 822 | Status append(const T& other) { | 159 | 822 | return append( | 160 | 822 | reinterpret_cast<const uint8_t*>(other.data()), | 161 | 822 | reinterpret_cast<const uint8_t*>(other.data()) + other.size()); | 162 | 822 | } |
Unexecuted instantiation: jxl::Status jxl::PaddedBytes::append<jxl::PaddedBytes>(jxl::PaddedBytes const&) |
163 | | |
164 | 34.2k | Status append(const uint8_t* begin, const uint8_t* end) { |
165 | 34.2k | if (end > begin) { |
166 | 26.6k | size_t old_size = size(); |
167 | 26.6k | size_t new_size; |
168 | 26.6k | if (!SafeAdd(old_size, static_cast<size_t>(end - begin), new_size)) { |
169 | 0 | return JXL_FAILURE("PaddedBytes append: overflow"); |
170 | 0 | } |
171 | 26.6k | JXL_RETURN_IF_ERROR(resize(new_size)); |
172 | 26.6k | memcpy(data() + old_size, begin, end - begin); |
173 | 26.6k | } |
174 | 34.2k | return true; |
175 | 34.2k | } |
176 | | |
177 | | private: |
178 | 12.3k | Status Init(size_t size) { |
179 | 12.3k | size_ = size; |
180 | 12.3k | return reserve(size); |
181 | 12.3k | } |
182 | | |
183 | 66.7M | void BoundsCheck(size_t i) const { |
184 | | // <= is safe due to padding and required by BitWriter. |
185 | 66.7M | JXL_DASSERT(i <= size()); |
186 | 66.7M | } |
187 | | |
188 | 116k | Status change_capacity(size_t new_capacity) { |
189 | 116k | JXL_DASSERT(new_capacity >= size_); |
190 | | |
191 | 116k | new_capacity = std::max<size_t>(64, new_capacity); |
192 | | |
193 | | // BitWriter writes up to 7 bytes past the end. |
194 | 116k | size_t padded_capacity; |
195 | 116k | if (!SafeAdd(new_capacity, static_cast<size_t>(8), padded_capacity)) { |
196 | 0 | return JXL_FAILURE("PaddedBytes capacity too large"); |
197 | 0 | } |
198 | 116k | JXL_ASSIGN_OR_RETURN( |
199 | 116k | AlignedMemory new_data, |
200 | 116k | AlignedMemory::Create(memory_manager_, padded_capacity)); |
201 | | |
202 | 116k | if (data_.address<void>() == nullptr) { |
203 | | // First allocation: ensure first byte is initialized (won't be copied). |
204 | 84.0k | new_data.address<uint8_t>()[0] = 0; |
205 | 84.0k | } else { |
206 | | // Subsequent resize: copy existing data to new location. |
207 | 32.6k | memmove(new_data.address<void>(), data_.address<void>(), size_); |
208 | | // Ensure that the first new byte is initialized, to allow write_bits to |
209 | | // safely append to the newly-resized PaddedBytes. |
210 | 32.6k | new_data.address<uint8_t>()[size_] = 0; |
211 | 32.6k | } |
212 | | |
213 | 116k | capacity_ = new_capacity; |
214 | 116k | data_ = std::move(new_data); |
215 | 116k | return true; |
216 | 116k | } |
217 | | |
218 | | JxlMemoryManager* memory_manager_; |
219 | | size_t size_; |
220 | | size_t capacity_; |
221 | | AlignedMemory data_; |
222 | | }; |
223 | | |
224 | | } // namespace jxl |
225 | | |
226 | | #endif // LIB_JXL_BASE_PADDED_BYTES_H_ |