Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add better stream output to overflow allocator #3

Merged
merged 2 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
name: Docs

on:
push:
branches: [ master ]
paths: '**/*.md'
workflow_dispatch:

jobs:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
paths-ignore:
- '**/*.md'
- '**/*.gitignore'
- '**/Doxyfile'

jobs:
Build:
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ This library also contains various containers that are STL compliant.
| `size_t size() const` | Returns the current amount of elements in the vector. |

# Allocator Examples
The following examples all have `using namespace ktl` or equivalent at the top for brevity.
These examples can all be seen as unit tests in [`src/test/exotic_allocator_test.cpp`](https://github.com/KredeGC/KTL/tree/master/src/test/exotic_allocator_test.cpp).

Create an allocator which will attempt to use a linear allocator for allocation, but fall back on malloc when full.
```cpp
// Create the allocator from some 8kb buffer and straight malloc
Expand Down
83 changes: 75 additions & 8 deletions include/ktl/allocators/overflow.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ namespace ktl
typedef typename detail::get_size_type_t<Alloc> size_type;

private:
static constexpr int OVERFLOW_PATTERN = 0b01010101010101010101010101010101;
static constexpr int64_t OVERFLOW_TEST = 0b0101010101010101010101010101010101010101010101010101010101010101;
static constexpr size_t OVERFLOW_SIZE = 8;
static constexpr unsigned int OVERFLOW_PATTERN = 0b10100101101001011010010110100101;
static constexpr unsigned char OVERFLOW_TEST = 0b10100101;
static constexpr size_t OVERFLOW_SIZE = 64;

public:
/**
* @brief Construct the allocator with a reference to a stream object
* @param stream The stream to use when leaks or corruption happens
*/
explicit overflow(Stream& stream) noexcept :
m_Stream(stream),
m_Alloc(),
Expand Down Expand Up @@ -85,6 +89,13 @@ namespace ktl
}

#pragma region Allocation
/**
* @brief Attempts to allocate a chunk of memory defined by @p n
* @note Allocates 64 bytes more on either side of the returned address.
* This memory will be used for overflow checking.
* @param n The amount of bytes to allocate memory for
* @return A location in memory that is at least @p n bytes big or nullptr if it could not be allocated
*/
void* allocate(size_type n)
{
m_Allocs += n;
Expand All @@ -101,6 +112,11 @@ namespace ktl
return ptr + OVERFLOW_SIZE;
}

/**
* @brief Attempts to deallocate the memory at location @p p
* @param p The location in memory to deallocate
* @param n The size that was initially allocated
*/
void deallocate(void* p, size_type n)
{
KTL_ASSERT(p != nullptr);
Expand All @@ -109,14 +125,34 @@ namespace ktl

if (p)
{
char* ptr = reinterpret_cast<char*>(p);
unsigned char* ptr = reinterpret_cast<unsigned char*>(p);

size_t before = 0;
size_t after = 0;

// Check against corruption
if (std::memcmp(ptr + n, &OVERFLOW_TEST, OVERFLOW_SIZE) != 0)
m_Stream << "--------MEMORY CORRUPTION DETECTED--------\nThe area around " << reinterpret_cast<int*>(ptr + n) << " has been illegally modified\n";
for (unsigned char* i = ptr - 1; i >= ptr - OVERFLOW_SIZE; i--)
{
if (*i != OVERFLOW_TEST)
before = ptr - i;
}

for (unsigned char* i = ptr + n; i < ptr + n + OVERFLOW_SIZE; i++)
{
if (*i != OVERFLOW_TEST)
after = i - ptr - n + 1;
}

if (before || after)
{
m_Stream << "--------MEMORY CORRUPTION DETECTED--------\nThe area around " << p << " (" << n << " bytes) has been illegally modified\n";

if (std::memcmp(ptr - OVERFLOW_SIZE, &OVERFLOW_TEST, OVERFLOW_SIZE) != 0)
m_Stream << "--------MEMORY CORRUPTION DETECTED--------\nThe area around " << reinterpret_cast<int*>(ptr - OVERFLOW_SIZE) << " has been illegally modified\n";
if (before)
m_Stream << " Before (" << before << " bytes)\n";

if (after)
m_Stream << " After (" << after << " bytes)\n";
}

size_type size = n + OVERFLOW_SIZE * 2;
m_Alloc.deallocate(ptr - OVERFLOW_SIZE, size);
Expand All @@ -125,6 +161,13 @@ namespace ktl
#pragma endregion

#pragma region Construction
/**
* @brief Constructs an object of T with the given @p ...args at the given location
* @note Keeps track of the number of constructions
* @tparam ...Args The types of the arguments
* @param p The location of the object in memory
* @param ...args A range of arguments to use to construct the object
*/
template<typename T, typename... Args>
void construct(T* p, Args&&... args)
{
Expand All @@ -136,6 +179,11 @@ namespace ktl
::new(p) T(std::forward<Args>(args)...);
}

/**
* @brief Destructs an object of T at the given location
* @note Keeps track of the number of destructions
* @param p The location of the object in memory
*/
template<typename T>
void destroy(T* p)
{
Expand All @@ -149,13 +197,24 @@ namespace ktl
#pragma endregion

#pragma region Utility
/**
* @brief Returns the maximum size that an allocation can be
* @note Only defined if the underlying allocator defines it
* @return The maximum size an allocation may be
*/
template<typename A = Alloc>
typename std::enable_if<detail::has_max_size_v<A>, size_type>::type
max_size() const noexcept
{
return m_Alloc.max_size();
}

/**
* @brief Returns whether or not the allocator owns the given location in memory
* @note Only defined if the underlying allocator defines it
* @param p The location of the object in memory
* @return Whether the allocator owns @p p
*/
template<typename A = Alloc>
typename std::enable_if<detail::has_owns_v<A>, bool>::type
owns(void* p) const
Expand All @@ -164,11 +223,19 @@ namespace ktl
}
#pragma endregion

/**
* @brief Returns a reference to the underlying allocator
* @return The allocator
*/
Alloc& get_allocator()
{
return m_Alloc;
}

/**
* @brief Returns a const reference to the underlying allocator
* @return The allocator
*/
const Alloc& get_allocator() const
{
return m_Alloc;
Expand Down
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);
}
}