Skip to content

Commit

Permalink
merge develop
Browse files Browse the repository at this point in the history
  • Loading branch information
liss-h committed Jan 6, 2025
2 parents e57bf32 + 9d18263 commit d8d3dc3
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/code_testing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
uses: dice-group/cpp-conan-release-reusable-workflow/.github/actions/add_conan_provider@main

- name: Configure CMake
run: cmake -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" -DCMAKE_BUILD_TYPE=Debug -DWITH_SVECTOR=ON -DWITH_BOOST=ON -DBUILD_TESTING=On -DBUILD_EXAMPLES=On -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -G Ninja -B build .
run: cmake -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" -DCMAKE_BUILD_TYPE=Debug -DWITH_SVECTOR=ON -DWITH_BOOST=ON -DBUILD_TESTING=On -DBUILD_EXAMPLES=On -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -G Ninja -B build .
env:
CC: ${{ steps.install_cc.outputs.cc }}
CXX: ${{ steps.install_cc.outputs.cxx }}
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ It contains:
- `integral_template_variant`: A wrapper type for `std::variant` guarantees to only contain variants of the form `T<ix>` where $\texttt{ix}\in [\texttt{first},\texttt{last}]$ (inclusive).
- `for_{types,values,range}`: Compile time for loops for types, values or ranges
- `polymorphic_allocator`: Like `std::pmr::polymorphic_allocator` but with static dispatch
- `limit_allocator`: Allocator wrapper that limits the amount of memory that is allowed to be allocated
- `DICE_DEFER`/`DICE_DEFER_TO_SUCCES`/`DICE_DEFER_TO_FAIL`: On-the-fly RAII for types that do not support it natively (similar to go's `defer` keyword)
- `overloaded`: Composition for `std::variant` visitor lambdas
- `flex_array`: A combination of `std::array`, `std::span` and a `vector` with small buffer optimization
Expand Down Expand Up @@ -64,6 +65,10 @@ The problem with `mmap` allocations is that they will be placed at an arbitrary
therefore absolute pointers will cause segfaults if the segment is reloaded.
Which means: vtables will not work (because they use absolute pointers) and therefore you cannot use `std::pmr::polymorphic_allocator`.

### `limit_allocator`
Allocator wrapper that limits the amount of memory that can be allocated through the inner allocator.
If the limit is exceeded it will throw `std::bad_alloc`.

### `DICE_DEFER`/`DICE_DEFER_TO_SUCCES`/`DICE_DEFER_TO_FAIL`
A mechanism similar to go's `defer` keyword, which can be used to defer some action to scope exit.
The primary use-case for this is on-the-fly RAII-like resource management for types that do not support RAII (for example C types).
Expand Down
7 changes: 7 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,10 @@ target_link_libraries(example_variant2
dice-template-library::dice-template-library
)

add_executable(example_limit_allocator
example_limit_allocator.cpp)
target_link_libraries(example_limit_allocator
PRIVATE
dice-template-library::dice-template-library
)

26 changes: 26 additions & 0 deletions examples/example_limit_allocator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include <dice/template-library/limit_allocator.hpp>

#include <cassert>
#include <vector>


int main() {
std::vector<int, dice::template_library::limit_allocator<int>> vec{dice::template_library::limit_allocator<int>{3 * sizeof(int)}};
vec.push_back(1);
vec.push_back(2);

try {
vec.push_back(3);
assert(false);
} catch (...) {
}

vec.pop_back();
vec.push_back(4);

try {
vec.push_back(5);
assert(false);
} catch (...) {
}
}
176 changes: 176 additions & 0 deletions include/dice/template-library/limit_allocator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#ifndef DICE_TEMPLATELIBRARY_LIMITALLOCATOR_HPP
#define DICE_TEMPLATELIBRARY_LIMITALLOCATOR_HPP

#include <atomic>
#include <cstddef>
#include <memory>
#include <new>
#include <type_traits>
#include <utility>

namespace dice::template_library {

/**
* The synchronization policy of a limit_allocator
*/
enum struct limit_allocator_syncness : bool {
sync, ///< thread-safe (synchronized)
unsync, ///< not thread-safe (unsynchronized)
};

namespace detail_limit_allocator {
template<limit_allocator_syncness syncness>
struct limit_allocator_control_block;

template<>
struct limit_allocator_control_block<limit_allocator_syncness::sync> {
std::atomic<size_t> bytes_left = 0;

void allocate(size_t n_bytes) {
auto old = bytes_left.load(std::memory_order_relaxed);

do {
if (old < n_bytes) [[unlikely]] {
throw std::bad_alloc{};
}
} while (!bytes_left.compare_exchange_weak(old, old - n_bytes, std::memory_order_relaxed, std::memory_order_relaxed));
}

void deallocate(size_t n_bytes) noexcept {
bytes_left.fetch_add(n_bytes, std::memory_order_relaxed);
}
};

template<>
struct limit_allocator_control_block<limit_allocator_syncness::unsync> {
size_t bytes_left = 0;

void allocate(size_t n_bytes) {
if (bytes_left < n_bytes) [[unlikely]] {
throw std::bad_alloc{};
}
bytes_left -= n_bytes;
}

void deallocate(size_t n_bytes) noexcept {
bytes_left += n_bytes;
}
};
}// namespace detail_limit_allocator

/**
* Allocator wrapper that limits the amount of memory its underlying allocator
* is allowed to allocate.
*
* @tparam T value type of the allocator (the thing that it allocates)
* @tparam Allocator the underlying allocator
* @tparam syncness determines the synchronization of the limit
*/
template<typename T, template<typename> typename Allocator = std::allocator, limit_allocator_syncness syncness = limit_allocator_syncness::sync>
struct limit_allocator {
using control_block_type = detail_limit_allocator::limit_allocator_control_block<syncness>;
using value_type = T;
using upstream_allocator_type = Allocator<T>;
using pointer = typename std::allocator_traits<upstream_allocator_type>::pointer;
using const_pointer = typename std::allocator_traits<upstream_allocator_type>::const_pointer;
using void_pointer = typename std::allocator_traits<upstream_allocator_type>::void_pointer;
using const_void_pointer = typename std::allocator_traits<upstream_allocator_type>::const_void_pointer;
using size_type = typename std::allocator_traits<upstream_allocator_type>::size_type;
using difference_type = typename std::allocator_traits<upstream_allocator_type>::difference_type;

using propagate_on_container_copy_assignment = typename std::allocator_traits<upstream_allocator_type>::propagate_on_container_copy_assignment;
using propagate_on_container_move_assignment = typename std::allocator_traits<upstream_allocator_type>::propagate_on_container_move_assignment;
using propagate_on_container_swap = typename std::allocator_traits<upstream_allocator_type>::propagate_on_container_swap;
using is_always_equal = std::false_type;

template<typename U>
struct rebind {
using other = limit_allocator<U, Allocator, syncness>;
};

private:
template<typename, template<typename> typename, limit_allocator_syncness>
friend struct limit_allocator;

std::shared_ptr<control_block_type> control_block_;
[[no_unique_address]] upstream_allocator_type inner_;

constexpr limit_allocator(std::shared_ptr<control_block_type> const &control_block, upstream_allocator_type const &alloc)
requires(std::is_default_constructible_v<upstream_allocator_type>)
: control_block_{control_block},
inner_{alloc} {
}

public:
explicit constexpr limit_allocator(size_t bytes_limit)
requires(std::is_default_constructible_v<upstream_allocator_type>)
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
inner_{} {
}

constexpr limit_allocator(limit_allocator const &other) noexcept(std::is_nothrow_move_constructible_v<upstream_allocator_type>) = default;
constexpr limit_allocator(limit_allocator &&other) noexcept(std::is_nothrow_copy_constructible_v<upstream_allocator_type>) = default;
constexpr limit_allocator &operator=(limit_allocator const &other) noexcept(std::is_nothrow_copy_assignable_v<upstream_allocator_type>) = default;
constexpr limit_allocator &operator=(limit_allocator &&other) noexcept(std::is_nothrow_move_assignable_v<upstream_allocator_type>) = default;
constexpr ~limit_allocator() = default;

template<typename U>
constexpr limit_allocator(limit_allocator<U, Allocator> const &other) noexcept(std::is_nothrow_constructible_v<upstream_allocator_type, typename limit_allocator<U, Allocator>::upstream_allocator_type const &>)
: control_block_{other.control_block_},
inner_{other.inner_} {
}

constexpr limit_allocator(size_t bytes_limit, upstream_allocator_type const &upstream)
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
inner_{upstream} {
}

constexpr limit_allocator(size_t bytes_limit, upstream_allocator_type &&upstream)
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
inner_{std::move(upstream)} {
}

template<typename... Args>
explicit constexpr limit_allocator(size_t bytes_limit, std::in_place_t, Args &&...args)
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
inner_{std::forward<Args>(args)...} {
}

constexpr pointer allocate(size_t n) {
control_block_->allocate(n * sizeof(T));

try {
return std::allocator_traits<upstream_allocator_type>::allocate(inner_, n);
} catch (...) {
control_block_->deallocate(n * sizeof(T));
throw;
}
}

constexpr void deallocate(pointer ptr, size_t n) {
std::allocator_traits<upstream_allocator_type>::deallocate(inner_, ptr, n);
control_block_->deallocate(n * sizeof(T));
}

constexpr limit_allocator select_on_container_copy_construction() const {
return limit_allocator{control_block_, std::allocator_traits<upstream_allocator_type>::select_on_container_copy_construction(inner_)};
}

[[nodiscard]] upstream_allocator_type const &upstream_allocator() const noexcept {
return inner_;
}

friend constexpr void swap(limit_allocator &a, limit_allocator &b) noexcept(std::is_nothrow_swappable_v<upstream_allocator_type>)
requires(std::is_swappable_v<upstream_allocator_type>)
{
using std::swap;
swap(a.control_block_, b.control_block_);
swap(a.inner_, b.inner_);
}

bool operator==(limit_allocator const &other) const noexcept = default;
bool operator!=(limit_allocator const &other) const noexcept = default;
};
}// namespace dice::template_library

#endif// DICE_TEMPLATELIBRARY_LIMITALLOCATOR_HPP
5 changes: 5 additions & 0 deletions include/dice/template-library/polymorphic_allocator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,11 @@ namespace dice::template_library {
using propagate_on_container_swap = typename std::allocator_traits<upstream_allocator_type>::propagate_on_container_swap;
using is_always_equal = typename std::allocator_traits<upstream_allocator_type>::is_always_equal;

template<typename U>
struct rebind {
using other = offset_ptr_stl_allocator<U, Allocator>;
};

private:
template<typename, template<typename> typename>
friend struct offset_ptr_stl_allocator;
Expand Down
73 changes: 31 additions & 42 deletions include/dice/template-library/variant2.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@
#include <utility>
#include <variant>

#define DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(noexcept_spec, action_block) \
if constexpr (noexcept_spec) { \
action_block \
} else { \
try { \
action_block \
} catch (...) { \
discriminant_ = discriminant_type::ValuelessByException; \
throw; \
} \
}


namespace dice::template_library {
template<typename T, typename U>
struct variant2;
Expand Down Expand Up @@ -314,13 +327,10 @@ namespace dice::template_library {
break;
}
case discriminant_type::Second: {
try {
a_.~T();
a_.~T();
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_copy_constructible_v<U>, {
new (&b_) U{other.b_};
} catch (...) {
discriminant_ = discriminant_type::ValuelessByException;
throw;
}
});
break;
}
case discriminant_type::ValuelessByException: {
Expand All @@ -337,13 +347,10 @@ namespace dice::template_library {
case discriminant_type::Second: {
switch (other.discriminant_) {
case discriminant_type::First: {
try {
b_.~U();
b_.~U();
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_copy_constructible_v<T>, {
new (&a_) T{other.a_};
} catch (...) {
discriminant_ = discriminant_type::ValuelessByException;
throw;
}
});
break;
}
case discriminant_type::Second: {
Expand Down Expand Up @@ -410,12 +417,9 @@ namespace dice::template_library {
}
case discriminant_type::Second: {
a_.~T();
try {
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_move_constructible_v<U>, {
new (&b_) U{std::move(other.b_)};
} catch (...) {
discriminant_ = discriminant_type::ValuelessByException;
throw;
}
});
break;
}
case discriminant_type::ValuelessByException: {
Expand All @@ -433,12 +437,9 @@ namespace dice::template_library {
switch (other.discriminant_) {
case discriminant_type::First: {
b_.~U();
try {
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_move_constructible_v<T>, {
new (&a_) T{std::move(other.a_)};
} catch (...) {
discriminant_ = discriminant_type::ValuelessByException;
throw;
}
});
break;
}
case discriminant_type::Second: {
Expand Down Expand Up @@ -499,12 +500,9 @@ namespace dice::template_library {
}
case discriminant_type::Second: {
b_.~U();
try {
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_copy_constructible_v<T>, {
new (&a_) T{value};
} catch (...) {
discriminant_ = discriminant_type::ValuelessByException;
throw;
}
});
discriminant_ = discriminant_type::First;
break;
}
Expand Down Expand Up @@ -533,12 +531,9 @@ namespace dice::template_library {
}
case discriminant_type::Second: {
b_.~U();
try {
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_move_constructible_v<T>, {
new (&a_) T{std::move(value)};
} catch (...) {
discriminant_ = discriminant_type::ValuelessByException;
throw;
}
});
discriminant_ = discriminant_type::First;
break;
}
Expand All @@ -562,12 +557,9 @@ namespace dice::template_library {
switch (discriminant_) {
case discriminant_type::First: {
a_.~T();
try {
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_copy_constructible_v<U>, {
new (&b_) U{value};
} catch (...) {
discriminant_ = discriminant_type::ValuelessByException;
throw;
}
});
discriminant_ = discriminant_type::Second;
break;
}
Expand All @@ -594,12 +586,9 @@ namespace dice::template_library {
switch (discriminant_) {
case discriminant_type::First: {
a_.~T();
try {
DICE_TEMPLATELIBRARY_DETAIL_VARIANT2_TRY(std::is_nothrow_move_constructible_v<U>, {
new (&b_) U{std::move(value)};
} catch (...) {
discriminant_ = discriminant_type::ValuelessByException;
throw;
}
});
discriminant_ = discriminant_type::Second;
break;
}
Expand Down
Loading

0 comments on commit d8d3dc3

Please sign in to comment.