/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 | 105k | : memory_manager_(memory_manager), size_(0), capacity_(0) {} |
34 | | |
35 | | static StatusOr<PaddedBytes> WithInitialSpace( |
36 | 11.9k | JxlMemoryManager* memory_manager, size_t size) { |
37 | 11.9k | PaddedBytes result(memory_manager); |
38 | 11.9k | JXL_RETURN_IF_ERROR(result.Init(size)); |
39 | 11.9k | return result; |
40 | 11.9k | } |
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 | 53.3k | : memory_manager_(other.memory_manager_), |
49 | 53.3k | size_(other.size_), |
50 | 53.3k | capacity_(other.capacity_), |
51 | 53.3k | data_(std::move(other.data_)) { |
52 | 53.3k | other.size_ = other.capacity_ = 0; |
53 | 53.3k | } |
54 | 14.7k | PaddedBytes& operator=(PaddedBytes&& other) noexcept { |
55 | 14.7k | memory_manager_ = other.memory_manager_; |
56 | 14.7k | size_ = other.size_; |
57 | 14.7k | capacity_ = other.capacity_; |
58 | 14.7k | data_ = std::move(other.data_); |
59 | | |
60 | 14.7k | if (&other != this) { |
61 | 14.7k | other.size_ = other.capacity_ = 0; |
62 | 14.7k | } |
63 | 14.7k | return *this; |
64 | 14.7k | } |
65 | | |
66 | 25.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 | 756k | Status reserve(size_t capacity) { |
80 | 756k | if (capacity <= capacity_) return true; |
81 | | |
82 | 87.1k | size_t new_capacity = std::max(capacity, 3 * capacity_ / 2); |
83 | | |
84 | 87.1k | return change_capacity(new_capacity); |
85 | 756k | } |
86 | | |
87 | 2.24k | Status shrink_to_fit() { return change_capacity(size_); } |
88 | | |
89 | | // NOTE: unlike vector, this does not initialize the new data! |
90 | | // However, we guarantee that write_bits can safely append after |
91 | | // the resize, as we zero-initialize the first new byte of data. |
92 | | // If size < capacity(), does not invalidate the memory. |
93 | 742k | Status resize(size_t size) { |
94 | 742k | JXL_RETURN_IF_ERROR(reserve(size)); |
95 | 742k | size_ = size; |
96 | 742k | return true; |
97 | 742k | } |
98 | | |
99 | | // resize(size) plus explicit initialization of the new data with `value`. |
100 | 0 | Status resize(size_t size, uint8_t value) { |
101 | 0 | size_t old_size = size_; |
102 | 0 | JXL_RETURN_IF_ERROR(resize(size)); |
103 | 0 | if (size_ > old_size) { |
104 | 0 | memset(data() + old_size, value, size_ - old_size); |
105 | 0 | } |
106 | 0 | return true; |
107 | 0 | } |
108 | | |
109 | | // Amortized constant complexity due to exponential growth. |
110 | 189k | Status push_back(uint8_t x) { |
111 | 189k | if (size_ == capacity_) { |
112 | 2.45k | JXL_RETURN_IF_ERROR(reserve(capacity_ + 1)); |
113 | 2.45k | } |
114 | | |
115 | 189k | data_.address<uint8_t>()[size_++] = x; |
116 | 189k | return true; |
117 | 189k | } |
118 | | |
119 | 1.29M | size_t size() const { return size_; } |
120 | 0 | size_t capacity() const { return capacity_; } |
121 | | |
122 | 55.2M | uint8_t* data() { return data_.address<uint8_t>(); } |
123 | 177k | const uint8_t* data() const { return data_.address<uint8_t>(); } |
124 | | |
125 | | // std::vector operations implemented in terms of the public interface above. |
126 | | |
127 | 921 | void clear() { |
128 | | // Not passing on the Status, because resizing to 0 cannot fail. |
129 | 921 | static_cast<void>(resize(0)); |
130 | 921 | } |
131 | 19.5k | bool empty() const { return size() == 0; } |
132 | | |
133 | 0 | Status assign(std::initializer_list<uint8_t> il) { |
134 | 0 | JXL_RETURN_IF_ERROR(resize(il.size())); |
135 | 0 | memcpy(data(), il.begin(), il.size()); |
136 | 0 | return true; |
137 | 0 | } |
138 | | |
139 | 0 | uint8_t* begin() { return data(); } |
140 | 17.1k | const uint8_t* begin() const { return data(); } |
141 | 0 | uint8_t* end() { return begin() + size(); } |
142 | 8.55k | const uint8_t* end() const { return begin() + size(); } |
143 | | |
144 | 54.9M | uint8_t& operator[](const size_t i) { |
145 | 54.9M | BoundsCheck(i); |
146 | 54.9M | return data()[i]; |
147 | 54.9M | } |
148 | 137k | const uint8_t& operator[](const size_t i) const { |
149 | 137k | BoundsCheck(i); |
150 | 137k | return data()[i]; |
151 | 137k | } |
152 | | |
153 | | template <typename T> |
154 | 21.7k | Status append(const T& other) { |
155 | 21.7k | return append( |
156 | 21.7k | reinterpret_cast<const uint8_t*>(other.data()), |
157 | 21.7k | reinterpret_cast<const uint8_t*>(other.data()) + other.size()); |
158 | 21.7k | } jxl::Status jxl::PaddedBytes::append<std::__1::array<unsigned char, 4ul> >(std::__1::array<unsigned char, 4ul> const&) Line | Count | Source | 154 | 20.9k | Status append(const T& other) { | 155 | 20.9k | return append( | 156 | 20.9k | reinterpret_cast<const uint8_t*>(other.data()), | 157 | 20.9k | reinterpret_cast<const uint8_t*>(other.data()) + other.size()); | 158 | 20.9k | } |
jxl::Status jxl::PaddedBytes::append<std::__1::array<unsigned char, 128ul> >(std::__1::array<unsigned char, 128ul> const&) Line | Count | Source | 154 | 831 | Status append(const T& other) { | 155 | 831 | return append( | 156 | 831 | reinterpret_cast<const uint8_t*>(other.data()), | 157 | 831 | reinterpret_cast<const uint8_t*>(other.data()) + other.size()); | 158 | 831 | } |
|
159 | | |
160 | 21.7k | Status append(const uint8_t* begin, const uint8_t* end) { |
161 | 21.7k | if (end - begin > 0) { |
162 | 21.7k | size_t old_size = size(); |
163 | 21.7k | JXL_RETURN_IF_ERROR(resize(size() + (end - begin))); |
164 | 21.7k | memcpy(data() + old_size, begin, end - begin); |
165 | 21.7k | } |
166 | 21.7k | return true; |
167 | 21.7k | } |
168 | | |
169 | | private: |
170 | 11.9k | Status Init(size_t size) { |
171 | 11.9k | size_ = size; |
172 | 11.9k | return reserve(size); |
173 | 11.9k | } |
174 | | |
175 | 55.1M | void BoundsCheck(size_t i) const { |
176 | | // <= is safe due to padding and required by BitWriter. |
177 | 55.1M | JXL_DASSERT(i <= size()); |
178 | 55.1M | } |
179 | | |
180 | 89.4k | Status change_capacity(size_t new_capacity) { |
181 | 89.4k | JXL_DASSERT(new_capacity >= size_); |
182 | | |
183 | 89.4k | new_capacity = std::max<size_t>(64, new_capacity); |
184 | | |
185 | | // BitWriter writes up to 7 bytes past the end. |
186 | 89.4k | JXL_ASSIGN_OR_RETURN( |
187 | 89.4k | AlignedMemory new_data, |
188 | 89.4k | AlignedMemory::Create(memory_manager_, new_capacity + 8)); |
189 | | |
190 | 89.4k | if (data_.address<void>() == nullptr) { |
191 | | // First allocation: ensure first byte is initialized (won't be copied). |
192 | 58.9k | new_data.address<uint8_t>()[0] = 0; |
193 | 58.9k | } else { |
194 | | // Subsequent resize: copy existing data to new location. |
195 | 30.5k | memmove(new_data.address<void>(), data_.address<void>(), size_); |
196 | | // Ensure that the first new byte is initialized, to allow write_bits to |
197 | | // safely append to the newly-resized PaddedBytes. |
198 | 30.5k | new_data.address<uint8_t>()[size_] = 0; |
199 | 30.5k | } |
200 | | |
201 | 89.4k | capacity_ = new_capacity; |
202 | 89.4k | data_ = std::move(new_data); |
203 | 89.4k | return true; |
204 | 89.4k | } |
205 | | |
206 | | JxlMemoryManager* memory_manager_; |
207 | | size_t size_; |
208 | | size_t capacity_; |
209 | | AlignedMemory data_; |
210 | | }; |
211 | | |
212 | | } // namespace jxl |
213 | | |
214 | | #endif // LIB_JXL_BASE_PADDED_BYTES_H_ |