Skip to content

Commit

Permalink
Add basic packed_ptr type and test
Browse files Browse the repository at this point in the history
  • Loading branch information
KredeGC committed Mar 29, 2023
1 parent 392c83a commit 4317a2a
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 2 deletions.
70 changes: 70 additions & 0 deletions include/ktl/containers/packed_ptr.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#pragma once

#include "../utility/assert.h"
#include "../utility/bits.h"
#include "packed_ptr_fwd.h"

#include <type_traits>

namespace ktl
{
/**
* @brief A packed pointer-like type which takes advantage of any bits that don't get used due to alignment
* @tparam PtrT The pointer type to use
*/
template<typename PtrT, size_t Bits, size_t Alignment>
class packed_ptr
{
public:
static constexpr uintmax_t FREE_BITS = detail::log2(Alignment);

private:
static_assert(Bits <= FREE_BITS, "The number of bits in use cannot surpass the number of free bits");

static constexpr uintptr_t INT_MASK = ((1ULL << Bits) - 1);
static constexpr uintptr_t PTR_MASK = ~((1ULL << FREE_BITS) - 1);

public:
packed_ptr() noexcept :
m_Value(reinterpret_cast<uintptr_t>(nullptr)) {}

template<typename Int>
explicit packed_ptr(PtrT p, Int value) noexcept :
m_Value((reinterpret_cast<uintptr_t>(p) & PTR_MASK) | (static_cast<uintptr_t>(value) & INT_MASK))
{
// Pointer must be correctly aligned
KTL_ASSERT((reinterpret_cast<size_t>(p) & (Alignment - 1)) == 0);
}

operator bool() const noexcept { return m_Value; }

PtrT get_ptr() const noexcept { return reinterpret_cast<PtrT>(m_Value & PTR_MASK); }

template<typename Int>
Int get_int() const noexcept
{
static_assert(std::is_unsigned_v<Int>, "Packed integer must be unsigned");

return static_cast<Int>(m_Value & INT_MASK);
}

void set_ptr(PtrT p) noexcept
{
// Pointer must be correctly aligned
KTL_ASSERT((reinterpret_cast<size_t>(p) & (Alignment - 1)) == 0);

m_Value = (reinterpret_cast<uintptr_t>(p) & PTR_MASK) | (m_Value & INT_MASK);
}

template<typename Int>
void set_int(Int value) noexcept
{
static_assert(std::is_unsigned_v<Int>, "Packed integer must be unsigned");

m_Value = (m_Value & PTR_MASK) | (static_cast<uintptr_t>(value) & INT_MASK);
}

private:
uintptr_t m_Value;
};
}
9 changes: 9 additions & 0 deletions include/ktl/containers/packed_ptr_fwd.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#include <cstddef>

namespace ktl
{
template<typename PtrT, size_t Bits, size_t Alignment = alignof(PtrT)>
class packed_ptr;
}
6 changes: 4 additions & 2 deletions include/ktl/containers/trivial_vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -403,13 +403,13 @@ namespace ktl

/**
* @brief Erases all elements in a range.
* @param first An iterator pointing to the element first.
* @param first An iterator pointing to the first element.
* @param last An iterator pointing to the location after the last element.
* @return An iterator pointing to the element immidiately after the erased ones.
*/
iterator erase(const_iterator first, const_iterator last) noexcept
{
KTL_ASSERT(first >= last);
KTL_ASSERT(first <= last);
KTL_ASSERT(first >= m_Begin && last <= m_End);

std::memmove(const_cast<iterator>(first), last, (m_End - last) * sizeof(T));
Expand Down Expand Up @@ -462,5 +462,7 @@ namespace ktl
T* m_Begin;
T* m_End;
T* m_EndMax;

//std::conditional_t<sizeof(T) < 32, uint8_t[32], uint8_t*> m_Data;
};
}
1 change: 1 addition & 0 deletions include/ktl/ktl.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

// Containers
#include "containers/binary_heap.h"
#include "containers/packed_ptr.h"
#include "containers/trivial_array.h"
#include "containers/trivial_buffer.h"
#include "containers/trivial_vector.h"
20 changes: 20 additions & 0 deletions include/ktl/utility/bits.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#include <cstdint>

namespace ktl::detail
{
constexpr inline uintmax_t log2(uintmax_t n)
{
uintmax_t r = 0;

if (n >> 32) { r += 32U; n >>= 32U; }
if (n >> 16) { r += 16U; n >>= 16U; }
if (n >> 8) { r += 8U; n >>= 8U; }
if (n >> 4) { r += 4U; n >>= 4U; }
if (n >> 2) { r += 2U; n >>= 2U; }
if (n >> 1) { r += 1U; n >>= 1U; }

return r;
}
}
47 changes: 47 additions & 0 deletions src/test/packed_ptr_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include "shared/assert_utility.h"
#include "shared/test.h"
#include "shared/types.h"

#define KTL_DEBUG_ASSERT
#include "ktl/containers/packed_ptr.h"

#include <memory>

// Naming scheme: packed_ptr
// Contains tests that use the ktl::packed_ptr

namespace ktl::test::packed_ptr
{
KTL_ADD_TEST(test_packed_ptr_stack)
{
int t;
int* in_ptr = &t;
uint16_t in_value = 2;

ktl::packed_ptr<int*, 2, alignof(int)> pack(in_ptr, in_value);

KTL_TEST_ASSERT(pack);
KTL_TEST_ASSERT(in_ptr == pack.get_ptr());
KTL_TEST_ASSERT(in_value == pack.get_int<uint16_t>());
}

KTL_ADD_TEST(test_packed_ptr_malloc)
{
std::allocator<double> alloc;

double* in_ptr = alloc.allocate(1);
uint64_t in_value = 2;

// Using more than 2 bits may require full x64 support
ktl::packed_ptr<double*, 2> pack;

pack.set_ptr(in_ptr);
pack.set_int(in_value);

KTL_TEST_ASSERT(pack);
KTL_TEST_ASSERT(in_ptr == pack.get_ptr());
KTL_TEST_ASSERT(in_value == pack.get_int<uint64_t>());

alloc.deallocate(in_ptr, 1);
}
}

0 comments on commit 4317a2a

Please sign in to comment.