diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ecfd5a9..204a0707 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -434,6 +434,7 @@ if(WITH_TESTS) metadata_requirements_test options_test packed_int_vector_test + packed_ptr_test pcm_sample_transformer_test pcmaudio_categorizer_test speedometer_test diff --git a/include/dwarfs/internal/packed_ptr.h b/include/dwarfs/internal/packed_ptr.h new file mode 100644 index 00000000..632ccec0 --- /dev/null +++ b/include/dwarfs/internal/packed_ptr.h @@ -0,0 +1,88 @@ +/* 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 +#include + +namespace dwarfs::internal { + +namespace detail { + +template +struct underlying_type { + using type = T; +}; + +// specialization for enums +template + requires std::is_enum_v +struct underlying_type { + using type = std::underlying_type_t; +}; + +} // namespace detail + +template +class packed_ptr { + public: + using data_type = DataType; + static constexpr size_t data_bits = DataBits; + static constexpr size_t data_mask = (1 << data_bits) - 1; + + static_assert( + std::unsigned_integral::type>, + "data_type must be an unsigned integer type"); + + explicit packed_ptr(T* p = nullptr, data_type data = {}) + : p_(build_packed_ptr(p, data)) {} + + void set(T* p) { p_ = build_packed_ptr(p, get_data()); } + T* get() const { return reinterpret_cast(p_ & ~data_mask); } + + T* operator->() const { return get(); } + T& operator*() const { return *get(); } + T& operator[](std::ptrdiff_t i) const { return get()[i]; } + + data_type get_data() const { return static_cast(p_ & data_mask); } + void set_data(data_type data) { p_ = build_packed_ptr(get(), data); } + + private: + static uintptr_t build_packed_ptr(T* p, data_type data) { + auto value = reinterpret_cast(p); + auto data_value = static_cast(data); + if (value & data_mask) { + throw std::invalid_argument("pointer is not aligned"); + } + if (data_value & ~data_mask) { + throw std::invalid_argument("data out of bounds"); + } + return value | data_value; + } + + uintptr_t p_; +}; + +} // namespace dwarfs::internal diff --git a/test/packed_ptr_test.cpp b/test/packed_ptr_test.cpp new file mode 100644 index 00000000..6eca7084 --- /dev/null +++ b/test/packed_ptr_test.cpp @@ -0,0 +1,132 @@ +/* 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 + +#include + +using namespace dwarfs::internal; + +TEST(packed_ptr, initialize) { + { + packed_ptr pp; + + EXPECT_EQ(pp.get(), nullptr); + EXPECT_EQ(pp.get_data(), 0); + } + + { + alignas(8) uint32_t i = 42; + packed_ptr pp(&i); + + EXPECT_EQ(pp.get(), &i); + EXPECT_EQ(pp.get_data(), 0); + } + + { + packed_ptr pp(nullptr, 0x7); + + EXPECT_EQ(pp.get(), nullptr); + EXPECT_EQ(pp.get_data(), 0x7); + } + + { + alignas(8) uint32_t i = 42; + packed_ptr pp(&i, 0x7); + + EXPECT_EQ(pp.get(), &i); + EXPECT_EQ(pp.get_data(), 0x7); + EXPECT_EQ(*pp, 42); + } + + EXPECT_THAT( + [] { packed_ptr pp(nullptr, 0x8); }, + testing::ThrowsMessage("data out of bounds")); + + EXPECT_THAT( + [] { + packed_ptr pp(reinterpret_cast(0x100004), 0x7); + }, + testing::ThrowsMessage("pointer is not aligned")); +} + +TEST(packed_ptr, integral) { + using ptr_type = std::pair; + packed_ptr pp; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_vfirst), int>); + static_assert(std::is_same_vsecond), float>); + + alignas(4) ptr_type p = {42, 2.0f}; + pp.set(&p); + + EXPECT_EQ(pp.get(), &p); + EXPECT_EQ(pp.get_data(), 0); + EXPECT_EQ(pp->first, 42); + EXPECT_EQ(pp->second, 2.0f); + EXPECT_EQ(pp[0].first, 42); + EXPECT_EQ(pp[0].second, 2.0f); + + EXPECT_THAT( + [&] { pp.set_data(0x4); }, + testing::ThrowsMessage("data out of bounds")); + + pp.set_data(0x3); + + EXPECT_EQ(pp.get(), &p); + EXPECT_EQ(pp.get_data(), 0x3); + + EXPECT_THAT( + [&] { pp.set(reinterpret_cast(0x100001)); }, + testing::ThrowsMessage("pointer is not aligned")); +} + +TEST(packed_ptr, enumeration) { + using ptr_type = std::pair; + enum class test_enum : unsigned { A = 1, B, C, D }; + packed_ptr pp; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_vfirst), int>); + + alignas(4) ptr_type p = {42, 2.0f}; + pp.set(&p); + + EXPECT_EQ(pp.get(), &p); + EXPECT_EQ(pp.get_data(), static_cast(0)); + + pp.set_data(test_enum::B); + + EXPECT_EQ(pp.get(), &p); + EXPECT_EQ(pp.get_data(), test_enum::B); + + EXPECT_THAT( + [&] { pp.set_data(test_enum::D); }, + testing::ThrowsMessage("data out of bounds")); +}