padded_bytes.h (6338B)
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 <stddef.h> 12 #include <stdint.h> 13 #include <string.h> // memcpy 14 15 #include <algorithm> // max 16 #include <initializer_list> 17 #include <utility> // swap 18 19 #include "lib/jxl/base/compiler_specific.h" 20 #include "lib/jxl/base/status.h" 21 #include "lib/jxl/cache_aligned.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 PaddedBytes() : size_(0), capacity_(0) {} 33 34 explicit PaddedBytes(size_t size) : size_(size), capacity_(0) { 35 reserve(size); 36 } 37 38 PaddedBytes(size_t size, uint8_t value) : size_(size), capacity_(0) { 39 reserve(size); 40 if (size_ != 0) { 41 memset(data(), value, size); 42 } 43 } 44 45 PaddedBytes(const PaddedBytes& other) : size_(other.size_), capacity_(0) { 46 reserve(size_); 47 if (data() != nullptr) memcpy(data(), other.data(), size_); 48 } 49 PaddedBytes& operator=(const PaddedBytes& other) { 50 // Self-assignment is safe. 51 resize(other.size()); 52 if (data() != nullptr) memmove(data(), other.data(), size_); 53 return *this; 54 } 55 56 // default is not OK - need to set other.size_ to 0! 57 PaddedBytes(PaddedBytes&& other) noexcept 58 : size_(other.size_), 59 capacity_(other.capacity_), 60 data_(std::move(other.data_)) { 61 other.size_ = other.capacity_ = 0; 62 } 63 PaddedBytes& operator=(PaddedBytes&& other) noexcept { 64 size_ = other.size_; 65 capacity_ = other.capacity_; 66 data_ = std::move(other.data_); 67 68 if (&other != this) { 69 other.size_ = other.capacity_ = 0; 70 } 71 return *this; 72 } 73 74 void swap(PaddedBytes& other) noexcept { 75 std::swap(size_, other.size_); 76 std::swap(capacity_, other.capacity_); 77 std::swap(data_, other.data_); 78 } 79 80 // If current capacity is greater than requested, then no-op. Otherwise 81 // copies existing data to newly allocated "data_". If allocation fails, 82 // data() == nullptr and size_ = capacity_ = 0. 83 // The new capacity will be at least 1.5 times the old capacity. This ensures 84 // that we avoid quadratic behaviour. 85 void reserve(size_t capacity) { 86 if (capacity <= capacity_) return; 87 88 size_t new_capacity = std::max(capacity, 3 * capacity_ / 2); 89 new_capacity = std::max<size_t>(64, new_capacity); 90 91 // BitWriter writes up to 7 bytes past the end. 92 CacheAlignedUniquePtr new_data = AllocateArray(new_capacity + 8); 93 if (new_data == nullptr) { 94 // Allocation failed, discard all data to ensure this is noticed. 95 size_ = capacity_ = 0; 96 return; 97 } 98 99 if (data_ == nullptr) { 100 // First allocation: ensure first byte is initialized (won't be copied). 101 new_data[0] = 0; 102 } else { 103 // Subsequent resize: copy existing data to new location. 104 memcpy(new_data.get(), data_.get(), size_); 105 // Ensure that the first new byte is initialized, to allow write_bits to 106 // safely append to the newly-resized PaddedBytes. 107 new_data[size_] = 0; 108 } 109 110 capacity_ = new_capacity; 111 std::swap(new_data, data_); 112 } 113 114 // NOTE: unlike vector, this does not initialize the new data! 115 // However, we guarantee that write_bits can safely append after 116 // the resize, as we zero-initialize the first new byte of data. 117 // If size < capacity(), does not invalidate the memory. 118 void resize(size_t size) { 119 reserve(size); 120 size_ = (data() == nullptr) ? 0 : size; 121 } 122 123 // resize(size) plus explicit initialization of the new data with `value`. 124 void resize(size_t size, uint8_t value) { 125 size_t old_size = size_; 126 resize(size); 127 if (size_ > old_size) { 128 memset(data() + old_size, value, size_ - old_size); 129 } 130 } 131 132 // Amortized constant complexity due to exponential growth. 133 void push_back(uint8_t x) { 134 if (size_ == capacity_) { 135 reserve(capacity_ + 1); 136 if (data() == nullptr) return; 137 } 138 139 data_[size_++] = x; 140 } 141 142 size_t size() const { return size_; } 143 size_t capacity() const { return capacity_; } 144 145 uint8_t* data() { return data_.get(); } 146 const uint8_t* data() const { return data_.get(); } 147 148 // std::vector operations implemented in terms of the public interface above. 149 150 void clear() { resize(0); } 151 bool empty() const { return size() == 0; } 152 153 void assign(std::initializer_list<uint8_t> il) { 154 resize(il.size()); 155 memcpy(data(), il.begin(), il.size()); 156 } 157 158 uint8_t* begin() { return data(); } 159 const uint8_t* begin() const { return data(); } 160 uint8_t* end() { return begin() + size(); } 161 const uint8_t* end() const { return begin() + size(); } 162 163 uint8_t& operator[](const size_t i) { 164 BoundsCheck(i); 165 return data()[i]; 166 } 167 const uint8_t& operator[](const size_t i) const { 168 BoundsCheck(i); 169 return data()[i]; 170 } 171 172 uint8_t& back() { 173 JXL_ASSERT(size() != 0); 174 return data()[size() - 1]; 175 } 176 const uint8_t& back() const { 177 JXL_ASSERT(size() != 0); 178 return data()[size() - 1]; 179 } 180 181 template <typename T> 182 void append(const T& other) { 183 append(reinterpret_cast<const uint8_t*>(other.data()), 184 reinterpret_cast<const uint8_t*>(other.data()) + other.size()); 185 } 186 187 void append(const uint8_t* begin, const uint8_t* end) { 188 if (end - begin > 0) { 189 size_t old_size = size(); 190 resize(size() + (end - begin)); 191 memcpy(data() + old_size, begin, end - begin); 192 } 193 } 194 195 private: 196 void BoundsCheck(size_t i) const { 197 // <= is safe due to padding and required by BitWriter. 198 JXL_ASSERT(i <= size()); 199 } 200 201 size_t size_; 202 size_t capacity_; 203 CacheAlignedUniquePtr data_; 204 }; 205 206 template <typename T> 207 static inline void Append(const T& s, PaddedBytes* out, 208 size_t* JXL_RESTRICT byte_pos) { 209 memcpy(out->data() + *byte_pos, s.data(), s.size()); 210 *byte_pos += s.size(); 211 JXL_CHECK(*byte_pos <= out->size()); 212 } 213 214 } // namespace jxl 215 216 #endif // LIB_JXL_BASE_PADDED_BYTES_H_