diff --git a/CMakeLists.txt b/CMakeLists.txt
index c4329b2c..3ecfd5a9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -433,6 +433,7 @@ if(WITH_TESTS)
lazy_value_test
metadata_requirements_test
options_test
+ packed_int_vector_test
pcm_sample_transformer_test
pcmaudio_categorizer_test
speedometer_test
diff --git a/include/dwarfs/internal/packed_int_vector.h b/include/dwarfs/internal/packed_int_vector.h
new file mode 100644
index 00000000..2ac16909
--- /dev/null
+++ b/include/dwarfs/internal/packed_int_vector.h
@@ -0,0 +1,177 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/**
+ * \author Marcus Holland-Moritz (github@mhxnet.de)
+ * \copyright Copyright (c) Marcus Holland-Moritz
+ *
+ * This file is part of dwarfs.
+ *
+ * dwarfs is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dwarfs is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with dwarfs. If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+#include
+
+namespace dwarfs::internal {
+
+template
+class packed_int_vector {
+ public:
+ using value_type = T;
+ using bits_type = folly::Bits;
+ using underlying_type = typename bits_type::UnderlyingType;
+ using size_type = size_t;
+
+ static constexpr size_type bits_per_block{bits_type::bitsPerBlock};
+
+ class value_proxy {
+ public:
+ value_proxy(packed_int_vector& vec, size_type i)
+ : vec_{vec}
+ , i_{i} {}
+
+ operator T() const { return vec_.get(i_); }
+
+ value_proxy& operator=(T value) {
+ vec_.set(i_, value);
+ return *this;
+ }
+
+ private:
+ packed_int_vector& vec_;
+ size_type i_;
+ };
+
+ packed_int_vector() = default;
+ packed_int_vector(size_type bits)
+ : bits_{bits} {}
+ packed_int_vector(size_type bits, size_type size)
+ : size_{size}
+ , bits_{bits}
+ , data_{min_data_size(size, bits)} {}
+
+ packed_int_vector(packed_int_vector const&) = default;
+ packed_int_vector(packed_int_vector&&) = default;
+ packed_int_vector& operator=(packed_int_vector const&) = default;
+ packed_int_vector& operator=(packed_int_vector&&) = default;
+
+ void reset(size_type bits = 0, size_type size = 0) {
+ size_ = size;
+ bits_ = bits;
+ data_.clear();
+ data_.resize(min_data_size(size, bits));
+ }
+
+ void resize(size_type size) {
+ size_ = size;
+ data_.resize(min_data_size(size, bits_));
+ }
+
+ void reserve(size_type size) { data_.reserve(min_data_size(size, bits_)); }
+
+ void shrink_to_fit() { data_.shrink_to_fit(); }
+
+ size_type capacity() const {
+ return bits_ > 0 ? (data_.capacity() * bits_per_block) / bits_ : 0;
+ }
+
+ void clear() {
+ size_ = 0;
+ data_.clear();
+ }
+
+ size_type size() const { return size_; }
+ size_type bits() const { return bits_; }
+
+ size_type size_in_bytes() const {
+ return data_.size() * sizeof(underlying_type);
+ }
+
+ bool empty() const { return size_ == 0; }
+
+ T operator[](size_type i) const { return this->get(i); }
+
+ T at(size_type i) const {
+ if (i >= size_) {
+ throw std::out_of_range("packed_int_vector::at");
+ }
+ return this->get(i);
+ }
+
+ T get(size_type i) const {
+ return bits_ > 0 ? bits_type::get(data_.data(), i * bits_, bits_) : 0;
+ }
+
+ value_proxy operator[](size_type i) { return value_proxy{*this, i}; }
+
+ value_proxy at(size_type i) {
+ if (i >= size_) {
+ throw std::out_of_range("packed_int_vector::at");
+ }
+ return this->operator[](i);
+ }
+
+ void set(size_type i, T value) {
+ if (bits_ > 0) {
+ bits_type::set(data_.data(), i * bits_, bits_, value);
+ }
+ }
+
+ void push_back(T value) {
+ if (min_data_size(size_ + 1, bits_) > data_.size()) {
+ data_.resize(data_.size() + 1);
+ }
+ set(size_++, value);
+ }
+
+ void pop_back() {
+ if (size_ > 0) {
+ --size_;
+ }
+ if (min_data_size(size_, bits_) < data_.size()) {
+ data_.resize(data_.size() - 1);
+ }
+ }
+
+ T back() const { return get(size_ - 1); }
+
+ value_proxy back() { return this->operator[](size_ - 1); }
+
+ T front() const { return get(0); }
+
+ value_proxy front() { return this->operator[](0); }
+
+ std::vector unpack() const {
+ std::vector result(size_);
+ for (size_type i = 0; i < size_; ++i) {
+ result[i] = get(i);
+ }
+ return result;
+ }
+
+ private:
+ static constexpr size_type min_data_size(size_type size, size_type bits) {
+ return (size * bits + bits_per_block - 1) / bits_per_block;
+ }
+
+ size_type size_{0};
+ size_type bits_{0};
+ std::vector data_;
+};
+
+} // namespace dwarfs::internal
diff --git a/test/packed_int_vector_test.cpp b/test/packed_int_vector_test.cpp
new file mode 100644
index 00000000..066f7d0a
--- /dev/null
+++ b/test/packed_int_vector_test.cpp
@@ -0,0 +1,123 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/**
+ * \author Marcus Holland-Moritz (github@mhxnet.de)
+ * \copyright Copyright (c) Marcus Holland-Moritz
+ *
+ * This file is part of dwarfs.
+ *
+ * dwarfs is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dwarfs is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with dwarfs. If not, see .
+ */
+
+#include
+
+#include
+
+using namespace dwarfs::internal;
+
+TEST(packed_int_vector, basic) {
+ packed_int_vector vec(5);
+
+ vec.push_back(1);
+ vec.push_back(31);
+ vec.push_back(0);
+ vec.push_back(5);
+ vec.push_back(3);
+ vec.push_back(25);
+
+ EXPECT_EQ(vec.size(), 6);
+ EXPECT_EQ(vec.size_in_bytes(), 4);
+
+ EXPECT_EQ(vec[0], 1);
+ EXPECT_EQ(vec[1], 31);
+ EXPECT_EQ(vec[2], 0);
+ EXPECT_EQ(vec[3], 5);
+ EXPECT_EQ(vec[4], 3);
+ EXPECT_EQ(vec[5], 25);
+
+ vec[0] = 11;
+ EXPECT_EQ(vec[0], 11);
+
+ vec.at(5) = 0;
+ EXPECT_EQ(vec[5], 0);
+
+ vec.resize(10);
+ EXPECT_EQ(vec[1], 31);
+
+ EXPECT_THROW(vec.at(10), std::out_of_range);
+ EXPECT_THROW(vec.at(10) = 17, std::out_of_range);
+
+ auto const& cvec = vec;
+
+ EXPECT_EQ(cvec[0], 11);
+ EXPECT_EQ(cvec[5], 0);
+
+ EXPECT_THROW(cvec.at(10), std::out_of_range);
+
+ vec.resize(4);
+ vec.shrink_to_fit();
+
+ EXPECT_EQ(vec.capacity(), 6);
+
+ EXPECT_EQ(vec[0], 11);
+ EXPECT_FALSE(vec.empty());
+
+ vec.clear();
+
+ EXPECT_EQ(vec.size(), 0);
+ EXPECT_TRUE(vec.empty());
+ vec.shrink_to_fit();
+ EXPECT_EQ(vec.capacity(), 0);
+ EXPECT_EQ(vec.size_in_bytes(), 0);
+}
+
+TEST(packed_int_vector, signed_int) {
+ packed_int_vector vec(13);
+
+ for (int64_t i = -4096; i < 4096; ++i) {
+ vec.push_back(i);
+ }
+
+ EXPECT_EQ(vec.size(), 8192);
+ EXPECT_EQ(vec.size_in_bytes(), 13312);
+
+ EXPECT_EQ(vec.front(), -4096);
+ EXPECT_EQ(vec.back(), 4095);
+
+ vec.resize(4096);
+
+ for (int64_t i = 0; i < 4096; ++i) {
+ EXPECT_EQ(vec[i], i - 4096);
+ }
+
+ auto unpacked = vec.unpack();
+
+ for (int64_t i = 0; i < 4096; ++i) {
+ EXPECT_EQ(unpacked[i], i - 4096);
+ }
+}
+
+TEST(packed_int_vector, zero_bits) {
+ packed_int_vector vec(0);
+
+ for (uint32_t i = 0; i < 100; ++i) {
+ vec.push_back(0);
+ }
+
+ EXPECT_EQ(vec.size(), 100);
+ EXPECT_EQ(vec.size_in_bytes(), 0);
+
+ for (uint32_t i = 0; i < 100; ++i) {
+ EXPECT_EQ(vec[i], 0);
+ }
+}