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