Skip to content

Commit

Permalink
✨ 🐛 Support C++17 for atomic API
Browse files Browse the repository at this point in the history
Problem:
- This library required C++20 before now, but there is no reason the `atomic`
  API should need C++20.
- Depending on this library caused `stdx` to require C++20; most parts of `stdx`
  require only C++17.

Solution:
- Allow the `atomic` API to build with C++17.

Note:
- `conc::call_in_critical_section` uses `decltype([]{})` so still requires
  C++20. But the `atomic` part of this library is usable with C++17.
  • Loading branch information
elbeno committed Oct 16, 2024
1 parent 27de8e1 commit be6d71a
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 62 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
matrix:
compiler: [clang, gcc]
version: [12, 13, 16, 17, 18]
cxx_standard: [20]
cxx_standard: [17, 20]
stdlib: [libstdc++, libc++]
build_type: [Debug]
include:
Expand Down Expand Up @@ -139,7 +139,7 @@ jobs:
matrix:
compiler: [clang]
version: [14, 15]
cxx_standard: [20]
cxx_standard: [17, 20]
stdlib: [libstdc++, libc++]
build_type: [Debug]
include:
Expand Down
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ else()
cpmaddpackage("gh:intel/cicd-repo-infrastructure#3e2bef0")
endif()

if(NOT DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 20)
endif()

add_library(concurrency INTERFACE)
target_compile_features(concurrency INTERFACE cxx_std_20)
target_compile_features(concurrency INTERFACE cxx_std_${CMAKE_CXX_STANDARD})

target_sources(
concurrency
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ A careful reading of
recommended to understand how the C++ memory model defines well-formed
concurrent execution.

C++20 is required. The following compilers are supported:
C++20 is required to use the `conc` namespace functionality; C++17 will suffice
for the `atomic` namespace functionality. The following compilers are supported:

- clang 14 through 17
- clang 14 through 18
- gcc 12 through 13

See the [full documentation](https://intel.github.io/cpp-baremetal-concurrency/).
84 changes: 67 additions & 17 deletions include/conc/atomic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

#include <conc/concepts.hpp>

#include <concepts>
#include <atomic>
#include <memory>
#include <type_traits>

namespace atomic {
namespace detail {
Expand Down Expand Up @@ -93,72 +94,121 @@ struct standard_policy {
template <typename...> inline auto injected_policy = detail::standard_policy{};

template <typename... DummyArgs, typename T>
#if __cplusplus >= 202002L
requires(sizeof...(DummyArgs) == 0)
#endif
[[nodiscard]] auto load(T const &t,
std::memory_order mo = std::memory_order_seq_cst) -> T {
load_store_policy auto &p = injected_policy<DummyArgs...>;
#if __cplusplus >= 202002L
load_store_policy
#endif
auto &p = injected_policy<DummyArgs...>;
return p.load(t, mo);
}

template <typename... DummyArgs, typename T, std::convertible_to<T> U>
template <typename... DummyArgs, typename T, typename U,
typename = std::enable_if_t<std::is_convertible_v<U, T>>>
#if __cplusplus >= 202002L
requires(sizeof...(DummyArgs) == 0)
#endif
auto store(T &t, U value,
std::memory_order mo = std::memory_order_seq_cst) -> void {
load_store_policy auto &p = injected_policy<DummyArgs...>;
#if __cplusplus >= 202002L
load_store_policy
#endif
auto &p = injected_policy<DummyArgs...>;
auto v = static_cast<T>(value);
p.store(t, v, mo);
}

template <typename... DummyArgs, typename T, std::convertible_to<T> U>
template <typename... DummyArgs, typename T, typename U,
typename = std::enable_if_t<std::is_convertible_v<U, T>>>
#if __cplusplus >= 202002L
requires(sizeof...(DummyArgs) == 0)
#endif
[[nodiscard]] auto
exchange(T &t, U value, std::memory_order mo = std::memory_order_seq_cst) -> T {
exchange_policy auto &p = injected_policy<DummyArgs...>;
#if __cplusplus >= 202002L
exchange_policy
#endif
auto &p = injected_policy<DummyArgs...>;
auto v = static_cast<T>(value);
return p.exchange(t, v, mo);
}

template <typename... DummyArgs, typename T, std::convertible_to<T> U>
template <typename... DummyArgs, typename T, typename U,
typename = std::enable_if_t<std::is_convertible_v<U, T>>>
#if __cplusplus >= 202002L
requires(sizeof...(DummyArgs) == 0)
#endif
auto fetch_add(T &t, U value,
std::memory_order mo = std::memory_order_seq_cst) -> T {
add_sub_policy auto &p = injected_policy<DummyArgs...>;
#if __cplusplus >= 202002L
add_sub_policy
#endif
auto &p = injected_policy<DummyArgs...>;
return p.fetch_add(t, static_cast<T>(value), mo);
}

template <typename... DummyArgs, typename T, std::convertible_to<T> U>
template <typename... DummyArgs, typename T, typename U,
typename = std::enable_if_t<std::is_convertible_v<U, T>>>
#if __cplusplus >= 202002L
requires(sizeof...(DummyArgs) == 0)
#endif
auto fetch_sub(T &t, U value,
std::memory_order mo = std::memory_order_seq_cst) -> T {
add_sub_policy auto &p = injected_policy<DummyArgs...>;
#if __cplusplus >= 202002L
add_sub_policy
#endif
auto &p = injected_policy<DummyArgs...>;
return p.fetch_sub(t, static_cast<T>(value), mo);
}

template <typename... DummyArgs, typename T, std::convertible_to<T> U>
template <typename... DummyArgs, typename T, typename U,
typename = std::enable_if_t<std::is_convertible_v<U, T>>>
#if __cplusplus >= 202002L
requires(sizeof...(DummyArgs) == 0)
#endif
auto fetch_and(T &t, U value,
std::memory_order mo = std::memory_order_seq_cst) -> T {
bitwise_policy auto &p = injected_policy<DummyArgs...>;
#if __cplusplus >= 202002L
bitwise_policy
#endif
auto &p = injected_policy<DummyArgs...>;
return p.fetch_and(t, static_cast<T>(value), mo);
}

template <typename... DummyArgs, typename T, std::convertible_to<T> U>
template <typename... DummyArgs, typename T, typename U,
typename = std::enable_if_t<std::is_convertible_v<U, T>>>
#if __cplusplus >= 202002L
requires(sizeof...(DummyArgs) == 0)
#endif
auto fetch_or(T &t, U value,
std::memory_order mo = std::memory_order_seq_cst) -> T {
bitwise_policy auto &p = injected_policy<DummyArgs...>;
#if __cplusplus >= 202002L
bitwise_policy
#endif
auto &p = injected_policy<DummyArgs...>;
return p.fetch_or(t, static_cast<T>(value), mo);
}

template <typename... DummyArgs, typename T, std::convertible_to<T> U>
template <typename... DummyArgs, typename T, typename U,
typename = std::enable_if_t<std::is_convertible_v<U, T>>>
#if __cplusplus >= 202002L
requires(sizeof...(DummyArgs) == 0)
#endif
auto fetch_xor(T &t, U value,
std::memory_order mo = std::memory_order_seq_cst) -> T {
bitwise_policy auto &p = injected_policy<DummyArgs...>;
#if __cplusplus >= 202002L
bitwise_policy
#endif
auto &p = injected_policy<DummyArgs...>;
return p.fetch_xor(t, static_cast<T>(value), mo);
}

template <typename T> struct atomic_type : std::type_identity<T> {};
template <typename T> struct atomic_type {
using type = T;
};
template <typename T> using atomic_type_t = typename atomic_type<T>::type;

template <typename T>
Expand Down
4 changes: 4 additions & 0 deletions include/conc/concepts.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#if __cplusplus >= 202002L

#include <atomic>
#include <concepts>

Expand Down Expand Up @@ -50,3 +52,5 @@ concept bitwise_policy =
template <typename T>
concept policy = exchange_policy<T> and add_sub_policy<T> and bitwise_policy<T>;
} // namespace atomic

#endif
2 changes: 1 addition & 1 deletion include/conc/concurrency.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ template <typename Mutex = std::mutex> class standard_policy {
public:
template <typename Uniq = void, std::invocable F, std::predicate... Pred>
requires(sizeof...(Pred) < 2)
static auto call_in_critical_section(F &&f, auto &&...pred)
static auto call_in_critical_section(F &&f, Pred &&...pred)
-> decltype(std::forward<F>(f)()) {
while (true) {
[[maybe_unused]] std::lock_guard l{m<Uniq>};
Expand Down
70 changes: 36 additions & 34 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,41 +1,43 @@
add_unit_test(
concepts_test
CATCH2
FILES
concepts.cpp
LIBRARIES
warnings
concurrency)
if(${CMAKE_CXX_STANDARD} GREATER_EQUAL 20)
add_unit_test(
concepts_test
CATCH2
FILES
concepts.cpp
LIBRARIES
warnings
concurrency)

add_unit_test(
freestanding_conc_injected_policy_test
CATCH2
FILES
freestanding_conc_injected_policy.cpp
LIBRARIES
warnings
concurrency)
add_unit_test(
freestanding_conc_injected_policy_test
CATCH2
FILES
freestanding_conc_injected_policy.cpp
LIBRARIES
warnings
concurrency)

add_unit_test(
hosted_conc_injected_policy_test
CATCH2
FILES
hosted_conc_injected_policy.cpp
LIBRARIES
warnings
concurrency)
add_unit_test(
hosted_conc_injected_policy_test
CATCH2
FILES
hosted_conc_injected_policy.cpp
LIBRARIES
warnings
concurrency)

add_unit_test(
conc_standard_policy_test
CATCH2
FILES
conc_standard_policy.cpp
LIBRARIES
warnings
concurrency
pthread)
add_unit_test(
conc_standard_policy_test
CATCH2
FILES
conc_standard_policy.cpp
LIBRARIES
warnings
concurrency
pthread)

add_compile_fail_test(fail_no_conc_policy.cpp LIBRARIES concurrency)
add_compile_fail_test(fail_no_conc_policy.cpp LIBRARIES concurrency)
endif()

add_unit_test(
atomic_standard_policy_test
Expand Down
5 changes: 3 additions & 2 deletions test/atomic_cfg.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
#include <cstdint>
#include <type_traits>

template <>
struct atomic::atomic_type<bool> : std::type_identity<std::uint32_t> {};
template <> struct atomic::atomic_type<bool> {
using type = std::uint32_t;
};

template <>
constexpr inline auto atomic::alignment_of<std::uint8_t> = std::size_t{4};
5 changes: 4 additions & 1 deletion test/atomic_injected_policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <atomic>
#include <cstdint>
#include <type_traits>

namespace {
struct custom_policy {
Expand All @@ -22,9 +23,11 @@ struct custom_policy {

template <> inline auto atomic::injected_policy<> = custom_policy{};

#if __cplusplus >= 202002L
TEST_CASE("injected policy models load_store", "[atomic_injected_policy]") {
static_assert(atomic::load_store_policy<custom_policy>);
}
#endif

TEST_CASE("injected policy implements load", "[atomic_injected_policy]") {
std::uint32_t val{17};
Expand All @@ -39,7 +42,7 @@ TEST_CASE("injected policy implements store", "[atomic_injected_policy]") {

TEST_CASE("injected policy can inject different atomic types",
"[atomic_injected_policy]") {
static_assert(std::same_as<atomic::atomic_type_t<bool>, std::uint32_t>);
static_assert(std::is_same_v<atomic::atomic_type_t<bool>, std::uint32_t>);
static_assert(atomic::alignment_of<bool> == alignof(std::uint32_t));
}

Expand Down
6 changes: 4 additions & 2 deletions test/atomic_standard_policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_test_macros.hpp>

#include <concepts>
#include <cstdint>
#include <thread>
#include <type_traits>

#if __cplusplus >= 202002L
TEST_CASE("standard policy models concepts", "[atomic_standard_policy]") {
static_assert(atomic::load_store_policy<atomic::detail::standard_policy>);
static_assert(atomic::exchange_policy<atomic::detail::standard_policy>);
static_assert(atomic::add_sub_policy<atomic::detail::standard_policy>);
static_assert(atomic::bitwise_policy<atomic::detail::standard_policy>);
static_assert(atomic::policy<atomic::detail::standard_policy>);
}
#endif

TEST_CASE("standard policy implements load", "[atomic_standard_policy]") {
std::uint32_t val{17};
Expand Down Expand Up @@ -136,7 +138,7 @@ TEST_CASE("standard policy implements fetch_xor atomically",
TEMPLATE_TEST_CASE("standard policy has normal types",
"[atomic_standard_policy]", bool, std::uint8_t,
std::uint16_t, std::uint32_t, std::uint64_t) {
static_assert(std::same_as<atomic::atomic_type_t<TestType>, TestType>);
static_assert(std::is_same_v<atomic::atomic_type_t<TestType>, TestType>);
}

TEMPLATE_TEST_CASE("standard policy has normal alignment",
Expand Down

0 comments on commit be6d71a

Please sign in to comment.