padded_bytes.h (6302B)
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 : memory_manager_(memory_manager), size_(0), capacity_(0) {} 34 35 static StatusOr<PaddedBytes> WithInitialSpace( 36 JxlMemoryManager* memory_manager, size_t size) { 37 PaddedBytes result(memory_manager); 38 JXL_RETURN_IF_ERROR(result.Init(size)); 39 return result; 40 } 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 : memory_manager_(other.memory_manager_), 49 size_(other.size_), 50 capacity_(other.capacity_), 51 data_(std::move(other.data_)) { 52 other.size_ = other.capacity_ = 0; 53 } 54 PaddedBytes& operator=(PaddedBytes&& other) noexcept { 55 memory_manager_ = other.memory_manager_; 56 size_ = other.size_; 57 capacity_ = other.capacity_; 58 data_ = std::move(other.data_); 59 60 if (&other != this) { 61 other.size_ = other.capacity_ = 0; 62 } 63 return *this; 64 } 65 66 JxlMemoryManager* memory_manager() const { return memory_manager_; } 67 68 void swap(PaddedBytes& other) noexcept { 69 std::swap(memory_manager_, other.memory_manager_); 70 std::swap(size_, other.size_); 71 std::swap(capacity_, other.capacity_); 72 std::swap(data_, other.data_); 73 } 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 Status reserve(size_t capacity) { 80 if (capacity <= capacity_) return true; 81 82 size_t new_capacity = std::max(capacity, 3 * capacity_ / 2); 83 new_capacity = std::max<size_t>(64, new_capacity); 84 85 // BitWriter writes up to 7 bytes past the end. 86 JXL_ASSIGN_OR_RETURN( 87 AlignedMemory new_data, 88 AlignedMemory::Create(memory_manager_, new_capacity + 8)); 89 90 if (data_.address<void>() == nullptr) { 91 // First allocation: ensure first byte is initialized (won't be copied). 92 new_data.address<uint8_t>()[0] = 0; 93 } else { 94 // Subsequent resize: copy existing data to new location. 95 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 new_data.address<uint8_t>()[size_] = 0; 99 } 100 101 capacity_ = new_capacity; 102 data_ = std::move(new_data); 103 return true; 104 } 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 Status resize(size_t size) { 111 JXL_RETURN_IF_ERROR(reserve(size)); 112 size_ = size; 113 return true; 114 } 115 116 // resize(size) plus explicit initialization of the new data with `value`. 117 Status resize(size_t size, uint8_t value) { 118 size_t old_size = size_; 119 JXL_RETURN_IF_ERROR(resize(size)); 120 if (size_ > old_size) { 121 memset(data() + old_size, value, size_ - old_size); 122 } 123 return true; 124 } 125 126 // Amortized constant complexity due to exponential growth. 127 Status push_back(uint8_t x) { 128 if (size_ == capacity_) { 129 JXL_RETURN_IF_ERROR(reserve(capacity_ + 1)); 130 } 131 132 data_.address<uint8_t>()[size_++] = x; 133 return true; 134 } 135 136 size_t size() const { return size_; } 137 size_t capacity() const { return capacity_; } 138 139 uint8_t* data() { return data_.address<uint8_t>(); } 140 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 void clear() { 145 // Not passing on the Status, because resizing to 0 cannot fail. 146 static_cast<void>(resize(0)); 147 } 148 bool empty() const { return size() == 0; } 149 150 Status assign(std::initializer_list<uint8_t> il) { 151 JXL_RETURN_IF_ERROR(resize(il.size())); 152 memcpy(data(), il.begin(), il.size()); 153 return true; 154 } 155 156 uint8_t* begin() { return data(); } 157 const uint8_t* begin() const { return data(); } 158 uint8_t* end() { return begin() + size(); } 159 const uint8_t* end() const { return begin() + size(); } 160 161 uint8_t& operator[](const size_t i) { 162 BoundsCheck(i); 163 return data()[i]; 164 } 165 const uint8_t& operator[](const size_t i) const { 166 BoundsCheck(i); 167 return data()[i]; 168 } 169 170 template <typename T> 171 Status append(const T& other) { 172 return append( 173 reinterpret_cast<const uint8_t*>(other.data()), 174 reinterpret_cast<const uint8_t*>(other.data()) + other.size()); 175 } 176 177 Status append(const uint8_t* begin, const uint8_t* end) { 178 if (end - begin > 0) { 179 size_t old_size = size(); 180 JXL_RETURN_IF_ERROR(resize(size() + (end - begin))); 181 memcpy(data() + old_size, begin, end - begin); 182 } 183 return true; 184 } 185 186 private: 187 Status Init(size_t size) { 188 size_ = size; 189 return reserve(size); 190 } 191 192 void BoundsCheck(size_t i) const { 193 // <= is safe due to padding and required by BitWriter. 194 JXL_DASSERT(i <= size()); 195 } 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_