Skip to content

Commit

Permalink
Added basic implementation of memory module (#15)
Browse files Browse the repository at this point in the history
* Added `construct/destroy` algorithms for `memory` module

* Fixed formatting

* Updated `.codecov.yml`

* Added more unit tests for `relocate` algorithms

* Reworked `uninitialized_algorithms` and added more unit-tests

* Check return values from algorithms

* Added `uninitialized_storage` implementation
  • Loading branch information
akukh authored May 23, 2024
1 parent 062c600 commit d5cd48a
Show file tree
Hide file tree
Showing 31 changed files with 2,753 additions and 105 deletions.
4 changes: 3 additions & 1 deletion .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ comment:

ignore:
- "**/*-test.cpp" # ignore test harness code
- "**/detail"
- "**/config/*"
- "**/detail/*"
- "**/foundation/utility/*"
2 changes: 0 additions & 2 deletions cmake/project_settings.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -594,8 +594,6 @@ target_compile_options(flux::project_settings INTERFACE
-Wshadow
# Warn whenever a class has virtual functions and an accessible non-virtual destructor.
-Wnon-virtual-dtor
# Warn if old-style (C-style) cast to a non-void type is used within a C++ program.
-Wold-style-cast
# Warn whenever a pointer is cast such that the required alignment of the target is increased. For example, warn if
# a char* is cast to an int* on machines where integers can only be accessed at two- or four-byte boundaries.
-Wcast-align
Expand Down
33 changes: 27 additions & 6 deletions flux-config/flux/config/features.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
#pragma once

#if __has_attribute(__no_sanitize__)
# define FLUX_NO_SANITIZE(...) __attribute__((__no_sanitize__(__VA_ARGS__)))
#else
# define FLUX_NO_SANITIZE(...)
#endif

#if __has_cpp_attribute(__gnu__::__always_inline__)
# define FLUX_ALWAYS_INLINE [[__gnu__::__always_inline__]]
#elif __has_cpp_attribute(clang::always_inline)
# define FLUX_ALWAYS_INLINE [[clang::always_inline]]
#elif __has_cpp_attribute(msvc::forceinline)
# define FLUX_ALWAYS_INLINE [[msvc::forceinline]]
#else
# define FLUX_ALWAYS_INLINE /* nothing */
#endif

#if __has_cpp_attribute(msvc::no_unique_address)
// MSVC implements [[no_unique_address]] as a silent no-op currently. If/when MSVC breaks its C++
// ABI, it will be changed to work as intended. However, MSVC implements [[msvc::no_unique_address]]
Expand All @@ -9,7 +25,7 @@
// [[msvc::no_unique_address]] though. If/when it does implement [[msvc::no_unique_address]], this
// should be preferred though.
# define FLUX_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
#elif __has_cpp_attribute(no_unique_address)
#elif __has_cpp_attribute(no_unique_address) >= 201803
# define FLUX_NO_UNIQUE_ADDRESS [[no_unique_address]]
#else
// Note that this can be replaced by #error as soon as clang-cl implements msvc::no_unique_address,
Expand All @@ -19,8 +35,13 @@
# define FLUX_NO_UNIQUE_ADDRESS /* nothing */
#endif

#if __has_attribute(__no_sanitize__)
# define FLUX_NO_SANITIZE(...) __attribute__((__no_sanitize__(__VA_ARGS__)))
#else
# define FLUX_NO_SANITIZE(...)
#endif
namespace flux::config {
namespace detail {
struct empty_class_type {};
struct test_no_unique_address {
FLUX_NO_UNIQUE_ADDRESS int actual_data_member;
FLUX_NO_UNIQUE_ADDRESS empty_class_type no_space;
};
} // namespace detail
inline constexpr bool has_no_unique_address = sizeof(detail::test_no_unique_address) == sizeof(int);
} // namespace flux::config
6 changes: 6 additions & 0 deletions flux-foundation/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
flux_static_library(foundation
COMMON
TEST
"flux/foundation/memory/detail/constexpr_memcpy-test.cpp"
"flux/foundation/memory/construct-test.cpp"
"flux/foundation/memory/relocate-test.cpp"
"flux/foundation/memory/uninitialized_algorithms-test.cpp"
"flux/foundation/memory/uninitialized_storage-test.cpp"
SOURCE
"flux/foundation/dummy.cpp"
LINK
Expand Down
8 changes: 7 additions & 1 deletion flux-foundation/flux/foundation.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
#pragma once
#include <flux/config.hpp>
#include <flux/io.hpp>
#include <flux/meta.hpp>

#include <flux/foundation/utility.hpp>
// clang-format off
#include <flux/foundation/types.hpp>
#include <flux/foundation/utility.hpp>
#include <flux/foundation/memory.hpp>
// clang-format on
4 changes: 4 additions & 0 deletions flux-foundation/flux/foundation/memory.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#pragma once

#include <flux/foundation/memory/uninitialized_algorithms.hpp>
#include <flux/foundation/memory/uninitialized_storage.hpp>
50 changes: 50 additions & 0 deletions flux-foundation/flux/foundation/memory/construct-test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include <flux/foundation.hpp>

#include <catch2/catch.hpp>

using namespace flux;

struct A {
int i;
};

struct B {
A a;
float f;
char c;
};

TEST_CASE("fou::construct_in_place", "[flux-memory/construct.hpp]") {
SECTION("construct in place lvalue") {
alignas(A) std::byte buffer[sizeof(A)];
A* object = (A*)buffer;

int expected_value = 2024;
fou::construct_in_place(object, expected_value);
CHECK(object->i == expected_value);

fou::destroy_in_place(object);
}

SECTION("construct in place rvalue") {
alignas(A) std::byte buffer[sizeof(A)];
A* object = (A*)buffer;

fou::construct_in_place(object, 2025);
CHECK(object->i == 2025);

fou::destroy_in_place(object);
}

SECTION("construct in place variadic") {
alignas(B) std::byte buffer[sizeof(B)];
B* object = (B*)buffer;

fou::construct_in_place(object, A{10}, 3.14f, 'w');
CHECK(object->a.i == 10);
CHECK(object->f == 3.14f);
CHECK(object->c == 'w');

fou::destroy_in_place(object);
}
}
36 changes: 36 additions & 0 deletions flux-foundation/flux/foundation/memory/construct.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma once
#include <flux/foundation/memory/detail/construct_at.hpp>
#include <flux/foundation/memory/detail/to_address.hpp>

namespace flux::fou {

template <meta::forward_iterator Iterator, typename T>
constexpr void construct_in_place(Iterator iterator, T const& value) noexcept {
detail::construct_at(detail::to_address(iterator), value);
}
template <meta::forward_iterator Iterator, typename T>
constexpr void construct_in_place(Iterator iterator, T&& value) noexcept {
detail::construct_at(detail::to_address(iterator), ::std::move(value));
}
template <meta::forward_iterator Iterator, typename... Args>
constexpr void construct_in_place(Iterator iterator, Args&&... args) noexcept {
detail::construct_at(detail::to_address(iterator), ::std::forward<Args>(args)...);
}

template <meta::forward_iterator Iterator>
constexpr Iterator destroy_range(Iterator first, Iterator last) noexcept {
for (; first != last; ++first) {
detail::destroy_at(detail::to_address(first));
}
return first;
}

template <meta::input_iterator Iterator>
constexpr void destroy_in_place(Iterator iterator) noexcept {
using value_type = meta::iter_value_t<Iterator>;
if constexpr (meta::not_trivially_destructible<value_type>) {
detail::destroy_at(detail::to_address(iterator));
}
}

} // namespace flux::fou
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include <flux/foundation.hpp>

#include <catch2/catch.hpp>

constexpr unsigned char Banand[] = "Banand";
constexpr unsigned char Banane[] = "Banane";
constexpr unsigned char Bananf[] = "Bananf";

constexpr bool true1[] = {true, true, true};
constexpr bool true2[] = {true, true, true};
constexpr bool false1[] = {false, false, false};

using flux::fou::detail::constexpr_memcmp;
using flux::fou::detail::constexpr_memcmp_equal;
using flux::fou::detail::constexpr_memcpy;

constexpr bool char_copy() noexcept {
unsigned char dest[4];

constexpr_memcpy(dest, Banand + 1, sizeof dest);
unsigned char expected[] = "anan";
return constexpr_memcmp_equal(expected, dest, 4);
}

TEST_CASE("fou::detail::constexpr_memcpy", "[flux-memory/constexpr_memcpy.hpp]") {
SECTION("compile time") {
STATIC_REQUIRE(char_copy());
}

SECTION("runtime") {
unsigned char source[] = "once upon a midnight dreary...";
unsigned char dest[4];

constexpr_memcpy(dest, source, sizeof dest);
unsigned char expected[] = "once";
CHECK(constexpr_memcmp_equal(expected, dest, 4));
}
}

TEST_CASE("fou::detail::constexpr_memcmp", "[flux-memory/constexpr_memcpy.hpp]") {
STATIC_REQUIRE(constexpr_memcmp(Banane, Banand, 6) == 1);
STATIC_REQUIRE(constexpr_memcmp(Banane, Banane, 6) == 0);
STATIC_REQUIRE(constexpr_memcmp(Banane, Bananf, 6) == -1);
}

TEST_CASE("fou::detail::constexpr_memcmp_equal", "[flux-memory/constexpr_memcpy.hpp]") {
STATIC_REQUIRE_FALSE(constexpr_memcmp_equal(Banane, Banand, 6));
STATIC_REQUIRE(constexpr_memcmp_equal(Banane, Banane, 6));

STATIC_REQUIRE_FALSE(constexpr_memcmp_equal(true1, false1, 3));
STATIC_REQUIRE(constexpr_memcmp_equal(true1, true2, 3));
}
134 changes: 134 additions & 0 deletions flux-foundation/flux/foundation/memory/detail/constexpr_memcpy.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#pragma once
#include <flux/meta/datasizeof.hpp>

namespace flux::fou::detail {

template <typename T, typename U>
requires meta::trivially_lexicographically_comparable<T, U>
constexpr int constexpr_memcmp(T const* lhs, U const* rhs, ::std::size_t count = 1) noexcept {
if consteval {
if (1 == sizeof(T) && !meta::same_as<T, bool>) {
return __builtin_memcmp(lhs, rhs, count * sizeof(T));
}

// clang-format off
for (; count != 0; --count, ++lhs, ++rhs) {
if (*lhs < *rhs) return -1;
if (*rhs < *lhs) return 1;
}
// clang-format on
return 0;
}
return __builtin_memcmp(lhs, rhs, count * sizeof(T));
}

// clang-format off
template <typename T, typename U>
requires meta::trivially_equality_comparable<T, U>
constexpr bool constexpr_memcmp_equal(T const* lhs, U const* rhs, ::std::size_t count = 1) noexcept {
if consteval {
if (1 == sizeof(T) && meta::integer<T>) {
return __builtin_memcmp(lhs, rhs, count * sizeof(T)) == 0;
}

for (; count != 0; --count, ++lhs, ++rhs) {
if (!(*lhs == *rhs)) {
return false;
}
}
return true;
}
return __builtin_memcmp(lhs, rhs, count * sizeof(T)) == 0;
}
// clang-format on

// This function performs an assignment to an existing, already alive TriviallyCopyable object
// from another TriviallyCopyable object.
//
// It basically works around the fact that TriviallyCopyable objects are not required to be
// syntactically copy/move constructible or copy/move assignable. Technically, only one of the
// four operations is required to be syntactically valid -- but at least one definitely has to
// be valid.
// clang-format off
template <typename T, typename U>
requires meta::assignable<T&, U const&>
constexpr auto assign_trivially_copyable(T& dest, U const& src) noexcept {
dest = src;
return dest;
}

template <typename T, typename U>
requires (not meta::assignable<T&, U const&> and meta::assignable<T&, U&&>)
constexpr auto assign_trivially_copyable(T& dest, U& src) noexcept {
dest = static_cast<U&&>(src);
return dest;
}

template <typename T, typename U>
requires (not meta::assignable <T&, U const&> and
not meta::assignable <T&, U&&> and
meta::constructible<T&, U const&>)
constexpr auto assign_trivially_copyable(T& dest, U const& src) noexcept {
construct_at(addressof(dest), src);
return dest;
}

template <typename T, typename U>
requires (not meta::assignable <T&, U const&> and
not meta::assignable <T&, U&&> and
not meta::constructible<T&, U const&> and
meta::constructible<T&, U&&>)
constexpr auto assign_trivially_copyable(T& dest, U& src) noexcept {
construct_at(addressof(dest), static_cast<U&&>(src));
return dest;
}
// clang-format on

template <typename T, typename U>
requires meta::trivially_copyable<meta::remove_cv_t<U>>
constexpr auto constexpr_memcpy(T* dest, U const* src, ::std::size_t count = 1) noexcept {
if consteval {
if constexpr (meta::same_as<meta::remove_cv_t<T>, meta::remove_cv_t<U>>) {
__builtin_memcpy(dest, src, count * sizeof(T));
return dest;
} else {
for (::std::size_t i = 0; i != count; ++i) {
assign_trivially_copyable(dest[i], src[i]);
}
}
} else {
if (count > 0) {
__builtin_memcpy(dest, src, (count - 1) * sizeof(T) + meta::data_size_of<T>);
}
}
return dest;
}

template <typename T, typename U>
requires meta::trivially_copyable<meta::remove_cv_t<U>>
constexpr auto constexpr_memmove(T* dest, U const* src, ::std::size_t count = 1) noexcept {
if consteval {
if constexpr (meta::same_as<meta::remove_cv_t<T>, meta::remove_cv_t<U>>) {
__builtin_memmove(dest, src, count * sizeof(T));
return dest;
} else {
if (is_pointer_in_range(src, src + count, dest)) {
for (; count > 0; --count) {
assign_trivially_copyable(dest[count - 1], src[count - 1]);
}
} else {
for (::std::size_t i = 0; i != count; ++i) {
assign_trivially_copyable(dest[i], src[i]);
}
}
}
} else {
if (count > 0) {
__builtin_memmove(dest, src, (count - 1) * sizeof(T) + meta::data_size_of<T>);
}
}
return dest;
}
// clang-format on

} // namespace flux::fou::detail
Loading

0 comments on commit d5cd48a

Please sign in to comment.