Pixie
Loading...
Searching...
No Matches
packed.h
Go to the documentation of this file.
1#pragma once
2
7
8#include <pixie/detail/serialization.h>
10#include <pixie/serialization.h>
11#include <pixie/storage.h>
12#include <pixie/storage/aligned.h>
13#include <pixie/storage/read_only_view.h>
14
15#include <algorithm>
16#include <array>
17#include <bit>
18#include <concepts>
19#include <cstddef>
20#include <cstdint>
21#include <limits>
22#include <span>
23#include <stdexcept>
24#include <type_traits>
25#include <utility>
26
27namespace pixie {
28
42template <StorageImplementation Storage,
43 IntegerVectorValue Value = std::uint64_t>
44 requires(std::same_as<Storage, AlignedStorage> ||
45 std::same_as<Storage, ReadOnlyStorageView>)
47 : public IntegerVectorBase<BasicPackedIntegerVector<Storage, Value>, Value>,
48 public SerializationBase<BasicPackedIntegerVector<Storage, Value>> {
49 public:
51 using value_type = Value;
52
54 using size_type = std::size_t;
55
58
64 explicit BasicPackedIntegerVector(std::span<const value_type> values)
65 requires std::same_as<Storage, AlignedStorage>
67 inferred_width(values),
68 ExplicitTag{}) {}
69
81 BasicPackedIntegerVector(std::span<const value_type> values, size_type width)
82 requires std::same_as<Storage, AlignedStorage>
83 : BasicPackedIntegerVector(values, width, ExplicitTag{}) {}
84
87 size_type width() const { return width_; }
88
90 size_type size_impl() const { return size_; }
91
94 return read_field(logical_words(), position, width_);
95 }
96
98 void copy_to_impl(size_type begin, std::span<value_type> output) const {
99 for (size_type i = 0; i < output.size(); ++i) {
100 output[i] = value_at_impl(begin + i);
101 }
102 }
103
109 if constexpr (std::same_as<Storage, AlignedStorage>) {
110 return sizeof(*this) + storage_.allocated_bytes();
111 } else {
112 return sizeof(*this);
113 }
114 }
115
126 void serialize_impl(BinaryWriter& writer) const {
127 if (writer.size_bytes() % alignof(std::uint64_t) != 0) {
128 throw std::invalid_argument(
129 "Packed integer-vector serialization requires an aligned offset");
130 }
131
132 const Dimensions dimensions = checked_dimensions(size_, width_);
133 const std::size_t artifact_begin = writer.size_bytes();
134 detail::write_magic(writer, kSerializationMagic);
135 writer.write_u32(kSerializationVersion);
136 writer.write_u8(detail::kLittleEndianMarker);
137 writer.write_u8(static_cast<std::uint8_t>(kValueDigits));
138 writer.write_u8(static_cast<std::uint8_t>(width_));
139 writer.write_u8(0);
140 const std::size_t size_position = writer.write_u64_placeholder();
141 writer.write_size(size_);
142
143 const auto words = logical_words();
144 for (std::size_t i = 0; i < dimensions.word_count; ++i) {
145 std::uint64_t word = words[i];
146 if (i + 1 == dimensions.word_count &&
147 dimensions.bit_count % kWordBits != 0) {
148 word &= low_bits_mask(dimensions.bit_count % kWordBits);
149 }
150 writer.write_u64(word);
151 }
152 writer.patch_u64(size_position, static_cast<std::uint64_t>(
153 writer.size_bytes() - artifact_begin));
154 }
155
172 BinaryReader& reader,
173 DeserializationValidation validation =
175 BinaryReader candidate = reader;
176 const std::size_t available_size = candidate.remaining();
177 detail::require_magic(candidate, kSerializationMagic);
178 if (candidate.read_u32() != kSerializationVersion ||
179 candidate.read_u8() != detail::kLittleEndianMarker ||
180 candidate.read_u8() != kValueDigits) {
181 throw std::invalid_argument(
182 "Incompatible serialized packed integer vector");
183 }
184 const std::size_t width = candidate.read_u8();
185 if (candidate.read_u8() != 0 || width > kValueDigits) {
186 throw std::invalid_argument(
187 "Invalid serialized packed integer-vector width or reserved field");
188 }
189 const std::size_t artifact_size = detail::checked_artifact_size(
190 candidate.read_u64(), kSerializationHeaderBytes, available_size);
191 const std::size_t count = candidate.read_size();
192 const Dimensions dimensions = checked_dimensions(count, width);
193 if ((count == 0 && width != 0) ||
194 dimensions.byte_count != artifact_size - kSerializationHeaderBytes) {
195 throw std::invalid_argument(
196 "Serialized packed integer vector has inconsistent dimensions");
197 }
198 BinaryReader payload = candidate.read_subreader(dimensions.byte_count);
199
200 if (validation == DeserializationValidation::kFull) {
201 BinaryReader validation_reader(payload.remaining_bytes());
202 bool has_nonzero_word = false;
203 for (std::size_t i = 0; i < dimensions.word_count; ++i) {
204 const std::uint64_t word = validation_reader.read_u64();
205 has_nonzero_word = has_nonzero_word || word != 0;
206 if (i + 1 == dimensions.word_count &&
207 dimensions.bit_count % kWordBits != 0 &&
208 (word & ~low_bits_mask(dimensions.bit_count % kWordBits)) != 0) {
209 throw std::invalid_argument(
210 "Serialized packed integer vector has non-zero unused bits");
211 }
212 }
213 if (count != 0 && width != 0 && !has_nonzero_word) {
214 throw std::invalid_argument(
215 "Serialized all-zero integer vector has noncanonical width");
216 }
217 }
218
219 Storage storage;
220 if constexpr (std::same_as<Storage, AlignedStorage>) {
221 storage = AlignedStorage(word_aligned_bit_count(dimensions.word_count));
222 auto words = storage.writable_words64();
223 for (std::size_t i = 0; i < dimensions.word_count; ++i) {
224 words[i] = payload.read_u64();
225 }
226 } else {
227 if constexpr (std::endian::native != std::endian::little) {
228 throw std::invalid_argument(
229 "Packed integer-vector views require little-endian word order");
230 }
231 const auto bytes = payload.read_bytes(dimensions.byte_count);
232 if (dimensions.byte_count != 0 &&
233 reinterpret_cast<std::uintptr_t>(bytes.data()) %
234 alignof(std::uint64_t) !=
235 0) {
236 throw std::invalid_argument(
237 "Serialized packed integer-vector payload is not word aligned");
238 }
239 storage = ReadOnlyStorageView(bytes);
240 }
241 if (!payload.empty()) {
242 throw std::invalid_argument(
243 "Serialized packed integer vector has trailing payload bytes");
244 }
245
246 BasicPackedIntegerVector result(std::move(storage), count, width,
247 LoadTag{});
248 reader = candidate;
249 return result;
250 }
251
252 private:
253 struct Dimensions {
254 std::size_t bit_count;
255 std::size_t word_count;
256 std::size_t byte_count;
257 };
258 struct ExplicitTag {};
259 struct LoadTag {};
260
261 inline static constexpr std::array<std::uint8_t, 8> kSerializationMagic = {
262 'P', 'X', 'I', 'N', 'T', 'V', 'E', 'C'};
263 static constexpr std::uint32_t kSerializationVersion = 1;
264 static constexpr std::size_t kSerializationHeaderBytes = 32;
265 static constexpr std::size_t kValueDigits =
266 std::numeric_limits<value_type>::digits;
267 static constexpr size_type kWordBits =
268 std::numeric_limits<std::uint64_t>::digits;
269
270 BasicPackedIntegerVector(std::span<const value_type> values,
271 size_type width,
272 ExplicitTag)
273 requires std::same_as<Storage, AlignedStorage>
274 : size_(values.size()) {
275 if (width > kValueDigits) {
276 throw std::invalid_argument("Packed integer-vector width is too large");
277 }
278 bool all_zero = true;
279 for (const value_type value : values) {
280 all_zero = all_zero && value == 0;
281 if (required_width(value) > width) {
282 throw std::invalid_argument(
283 "Packed integer-vector value does not fit the width");
284 }
285 }
286 width_ = all_zero ? 0 : width;
287 const Dimensions dimensions = checked_dimensions(size_, width_);
288 storage_ = AlignedStorage(word_aligned_bit_count(dimensions.word_count));
289 auto words = storage_.writable_words64();
290 for (std::size_t i = 0; i < values.size(); ++i) {
291 write_field(words, i, width_, values[i]);
292 }
293 }
294
295 BasicPackedIntegerVector(Storage storage,
296 size_type size,
297 size_type width,
298 LoadTag)
299 : storage_(std::move(storage)), size_(size), width_(width) {}
300
301 static size_type required_width(value_type value) {
302 return static_cast<size_type>(std::bit_width(value));
303 }
304
305 static constexpr std::uint64_t low_bits_mask(size_type count) {
306 return count >= kWordBits ? std::numeric_limits<std::uint64_t>::max()
307 : (std::uint64_t{1} << count) - 1;
308 }
309
310 static size_type inferred_width(std::span<const value_type> values) {
311 size_type width = 0;
312 for (const value_type value : values) {
313 width = std::max(width, required_width(value));
314 }
315 return width;
316 }
317
318 static Dimensions checked_dimensions(size_type count, size_type width) {
319 if (width > kValueDigits) {
320 throw std::invalid_argument("Packed integer-vector width is too large");
321 }
322 if (width != 0 && count > std::numeric_limits<size_type>::max() / width) {
323 throw std::length_error("Packed integer-vector bit count is too large");
324 }
325 const size_type bit_count = count * width;
326 const size_type word_count =
327 bit_count == 0 ? 0 : 1 + (bit_count - 1) / kWordBits;
328 if (word_count >
329 std::numeric_limits<size_type>::max() / sizeof(std::uint64_t)) {
330 throw std::length_error("Packed integer-vector payload is too large");
331 }
332 return {bit_count, word_count, word_count * sizeof(std::uint64_t)};
333 }
334
335 static size_type word_aligned_bit_count(size_type word_count) {
336 if (word_count > std::numeric_limits<size_type>::max() / kWordBits) {
337 throw std::length_error("Packed integer-vector storage is too large");
338 }
339 return word_count * kWordBits;
340 }
341
342 std::span<const std::uint64_t> logical_words() const {
343 if (width_ == 0) {
344 return {};
345 }
346 const Dimensions dimensions = checked_dimensions(size_, width_);
347 return storage_.as_words64().first(dimensions.word_count);
348 }
349
350 static value_type read_field(std::span<const std::uint64_t> words,
351 size_type position,
352 size_type width) {
353 if (width == 0) {
354 return 0;
355 }
356 if (width == kWordBits) {
357 return static_cast<value_type>(words[position]);
358 }
359 const size_type bit_position = position * width;
360 const size_type word = bit_position / kWordBits;
361 const size_type offset = bit_position % kWordBits;
362 std::uint64_t value = words[word] >> offset;
363 if (offset + width > kWordBits) {
364 value |= words[word + 1] << (kWordBits - offset);
365 }
366 return static_cast<value_type>(value & low_bits_mask(width));
367 }
368
369 static void write_field(std::span<std::uint64_t> words,
370 size_type position,
371 size_type width,
372 value_type value) {
373 if (width == 0) {
374 return;
375 }
376 if (width == kWordBits) {
377 words[position] = static_cast<std::uint64_t>(value);
378 return;
379 }
380 const size_type bit_position = position * width;
381 const size_type word = bit_position / kWordBits;
382 const size_type offset = bit_position % kWordBits;
383 words[word] |= static_cast<std::uint64_t>(value) << offset;
384 if (offset + width > kWordBits) {
385 words[word + 1] |=
386 static_cast<std::uint64_t>(value) >> (kWordBits - offset);
387 }
388 }
389
390 Storage storage_;
391 size_type size_ = 0;
392 size_type width_ = 0;
393};
394
396template <IntegerVectorValue Value = std::uint64_t>
398
403template <IntegerVectorValue Value = std::uint64_t>
406
407} // namespace pixie
Owning storage with a logical byte size and 64-byte-aligned backing.
Definition aligned.h:41
Immutable runtime-width packed integer vector over configurable storage.
Definition packed.h:48
value_type value_at_impl(size_type position) const
Read one valid zero-based position without bounds checking.
Definition packed.h:93
size_type width() const
Definition packed.h:87
void serialize_impl(BinaryWriter &writer) const
Write a version-1 canonical little-endian packed-vector artifact.
Definition packed.h:126
BasicPackedIntegerVector(std::span< const value_type > values)
Pack values using their minimum required width.
Definition packed.h:64
size_type memory_usage_bytes_impl() const
Return inline bytes plus owned storage capacity.
Definition packed.h:108
Value value_type
Stored unsigned integer type.
Definition packed.h:51
BasicPackedIntegerVector()=default
Construct an empty width-zero vector.
size_type size_impl() const
Return the logical number of elements.
Definition packed.h:90
BasicPackedIntegerVector(std::span< const value_type > values, size_type width)
Pack values after validating an explicit maximum width.
Definition packed.h:81
static BasicPackedIntegerVector deserialize_impl(BinaryReader &reader, DeserializationValidation validation=DeserializationValidation::kQuick)
Restore one packed-vector artifact and advance reader on success.
Definition packed.h:171
void copy_to_impl(size_type begin, std::span< value_type > output) const
Copy a range already validated by the public facade.
Definition packed.h:98
std::size_t size_type
Type used for element counts, widths, and positions.
Definition packed.h:54
Bounds-checked reader for canonical little-endian binary data.
Definition serialization.h:526
std::size_t remaining() const noexcept
Return the number of unconsumed bytes.
Definition serialization.h:544
std::span< const std::byte > remaining_bytes() const noexcept
Return all currently unconsumed bytes.
Definition serialization.h:547
std::uint8_t read_u8()
Read an unsigned eight-bit integer.
Definition serialization.h:552
BinaryReader read_subreader(std::size_t count)
Read a bounded region as an independent child reader.
Definition serialization.h:615
std::span< const std::byte > read_bytes(std::size_t count)
Read exactly count uninterpreted bytes.
Definition serialization.h:604
std::uint64_t read_u64()
Read an unsigned little-endian 64-bit integer.
Definition serialization.h:561
std::uint32_t read_u32()
Read an unsigned little-endian 32-bit integer.
Definition serialization.h:558
bool empty() const noexcept
Return whether all input bytes have been consumed.
Definition serialization.h:532
std::size_t read_size()
Read an unsigned 64-bit size and convert it to size_t.
Definition serialization.h:587
Bounded-buffer writer for canonical little-endian binary data.
Definition serialization.h:198
void write_u32(std::uint32_t value)
Write an unsigned 32-bit integer in little-endian order.
Definition serialization.h:258
void write_u8(std::uint8_t value)
Write an unsigned eight-bit integer.
Definition serialization.h:252
std::size_t size_bytes() const noexcept
Return the logical number of bytes written.
Definition serialization.h:244
void patch_u64(std::size_t position, std::uint64_t value)
Replace an existing 64-bit field with value.
Definition serialization.h:373
std::size_t write_u64_placeholder()
Write a zero 64-bit field and return its byte position.
Definition serialization.h:363
void write_u64(std::uint64_t value)
Write an unsigned 64-bit integer in little-endian order.
Definition serialization.h:261
void write_size(std::size_t value)
Write a platform size as an unsigned 64-bit integer.
Definition serialization.h:287
CRTP facade for an immutable positional integer vector.
Definition integer_vector.h:40
A non-owning, read-only view of a byte sequence.
Definition read_only_view.h:15
CRTP facade for optional binary serialization and deserialization.
Definition serialization.h:693
An unsigned integer-vector value type no wider than 64 bits.
Definition integer_vector.h:23
A concrete CRTP implementation of StorageBase.
Definition storage.h:290
Common interfaces for immutable positional integer vectors.
BasicPackedIntegerVector< ReadOnlyStorageView, Value > PackedIntegerVectorView
Zero-copy read-only packed integer-vector view.
Definition packed.h:404
BasicPackedIntegerVector< AlignedStorage, Value > PackedIntegerVector
Owning aligned packed integer vector.
Definition packed.h:397
Checked byte-oriented binary serialization primitives.
DeserializationValidation
Validation strength used while restoring serialized indexes.
Definition serialization.h:28
@ kQuick
Check framing, dimensions, references, and other conditions needed for memory-safe terminating querie...
Definition serialization.h:33
@ kFull
Additionally authenticate all source-derived metadata against the supplied source contents.
Definition serialization.h:39
Common interface for byte-addressable storage.