Skip to content
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
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ endif()

add_versioned_package("gh:boostorg/mp11#boost-1.83.0")
fmt_recipe(10.2.1)
add_versioned_package("gh:intel/cpp-baremetal-concurrency#27de8e1")

if(NOT DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 20)
Expand All @@ -25,7 +26,8 @@ add_library(stdx INTERFACE)
target_compile_features(stdx INTERFACE cxx_std_${CMAKE_CXX_STANDARD})
target_compile_options(
stdx INTERFACE $<$<CXX_COMPILER_ID:Clang>:-Wno-missing-braces>)
target_link_libraries_system(stdx INTERFACE boost_mp11 fmt::fmt-header-only)
target_link_libraries_system(stdx INTERFACE concurrency boost_mp11
fmt::fmt-header-only)

target_sources(
stdx
Expand Down
58 changes: 31 additions & 27 deletions include/stdx/atomic_bitset.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <conc/atomic.hpp>

#include <stdx/bit.hpp>
#include <stdx/bitset.hpp>
#include <stdx/compiler.hpp>
Expand All @@ -22,19 +24,22 @@ template <auto Size,
typename StorageElem = decltype(smallest_uint<to_underlying(Size)>())>
class atomic_bitset {
constexpr static std::size_t N = to_underlying(Size);
using elem_t = StorageElem;

using elem_t = atomic::atomic_type_t<StorageElem>;
constexpr static auto alignment = atomic::alignment_of<StorageElem>;

static_assert(std::is_unsigned_v<elem_t>,
"Storage element for atomic_bitset must be an unsigned type");

constexpr static auto bit = elem_t{1U};

static_assert(N <= std::numeric_limits<elem_t>::digits,
"atomic_bitset is limited to a single storage element");
std::atomic<elem_t> storage{};
alignas(alignment) elem_t storage{};

constexpr static auto mask = bit_mask<elem_t, N - 1>();
elem_t salient_value(std::memory_order order) const {
return storage.load(order) & mask;
auto salient_value(std::memory_order order) const -> elem_t {
return atomic::load(storage, order) & mask;
}

[[nodiscard]] static constexpr auto value_from_string(std::string_view str,
Expand Down Expand Up @@ -88,46 +93,44 @@ class atomic_bitset {
"Conversion must be to an unsigned integral type or enum!");
static_assert(N <= std::numeric_limits<U>::digits,
"atomic_bitset must fit within T");
return static_cast<T>(storage.load(order));
return static_cast<T>(salient_value(order));
}

[[nodiscard]] auto
to_natural(std::memory_order order = std::memory_order_seq_cst) const {
return storage.load(order);
to_natural(std::memory_order order = std::memory_order_seq_cst) const
-> StorageElem {
return static_cast<StorageElem>(salient_value(order));
}

operator bitset_t() const { return bitset_t{storage.load()}; }
operator bitset_t() const {
return bitset_t{salient_value(std::memory_order_seq_cst)};
}

auto load(std::memory_order order = std::memory_order_seq_cst) const
-> bitset_t {
return bitset_t{storage.load(order)};
return bitset_t{salient_value(order)};
}
auto store(bitset_t b,
std::memory_order order = std::memory_order_seq_cst) {
storage.store(b.template to<elem_t>(), order);
atomic::store(storage, b.template to<elem_t>(), order);
}

constexpr static std::integral_constant<std::size_t, N> size{};

constexpr static std::bool_constant<
std::atomic<elem_t>::is_always_lock_free>
is_always_lock_free{};

template <typename T> [[nodiscard]] auto operator[](T idx) const -> bool {
auto const pos = static_cast<std::size_t>(to_underlying(idx));
return (salient_value(std::memory_order_seq_cst) & (bit << pos)) != 0;
return load()[idx];
}

template <typename T>
auto set(T idx, bool value = true,
std::memory_order order = std::memory_order_seq_cst) -> bitset_t {
auto const pos = static_cast<std::size_t>(to_underlying(idx));
if (value) {
return bitset_t{
storage.fetch_or(static_cast<elem_t>(bit << pos), order)};
return bitset_t{atomic::fetch_or(
storage, static_cast<elem_t>(bit << pos), order)};
}
return bitset_t{
storage.fetch_and(static_cast<elem_t>(~(bit << pos)), order)};
return bitset_t{atomic::fetch_and(
storage, static_cast<elem_t>(~(bit << pos)), order)};
}

auto set(lsb_t lsb, msb_t msb, bool value = true,
Expand All @@ -136,9 +139,9 @@ class atomic_bitset {
auto const m = to_underlying(msb);
auto const shifted_value = bit_mask<elem_t>(m, l);
if (value) {
return bitset_t{storage.fetch_or(shifted_value, order)};
return bitset_t{atomic::fetch_or(storage, shifted_value, order)};
}
return bitset_t{storage.fetch_and(~shifted_value, order)};
return bitset_t{atomic::fetch_and(storage, ~shifted_value, order)};
}

auto set(lsb_t lsb, length_t len, bool value = true,
Expand All @@ -150,18 +153,19 @@ class atomic_bitset {

auto set(std::memory_order order = std::memory_order_seq_cst)
LIFETIMEBOUND -> atomic_bitset & {
storage.store(mask, order);
atomic::store(storage, mask, order);
return *this;
}

template <typename T> auto reset(T idx) -> bitset_t {
auto const pos = static_cast<std::size_t>(to_underlying(idx));
return bitset_t{storage.fetch_and(static_cast<elem_t>(~(bit << pos)))};
return bitset_t{
atomic::fetch_and(storage, static_cast<elem_t>(~(bit << pos)))};
}

auto reset(std::memory_order order = std::memory_order_seq_cst)
LIFETIMEBOUND -> atomic_bitset & {
storage.store(elem_t{}, order);
atomic::store(storage, elem_t{}, order);
return *this;
}

Expand All @@ -182,11 +186,11 @@ class atomic_bitset {
std::memory_order order = std::memory_order_seq_cst) -> bitset_t {
auto const pos = static_cast<std::size_t>(to_underlying(idx));
return bitset_t{
storage.fetch_xor(static_cast<elem_t>(bit << pos), order)};
atomic::fetch_xor(storage, static_cast<elem_t>(bit << pos), order)};
}

auto flip(std::memory_order order = std::memory_order_seq_cst) -> bitset_t {
return bitset_t{storage.fetch_xor(mask, order)};
return bitset_t{atomic::fetch_xor(storage, mask, order)};
}

[[nodiscard]] auto
Expand Down
5 changes: 5 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ add_tests(
algorithm
always_false
atomic_bitset
atomic_bitset_override
bind
bit
bitset
Expand Down Expand Up @@ -60,6 +61,10 @@ add_tests(
utility
udls)

target_compile_definitions(
atomic_bitset_override_test
PRIVATE -DATOMIC_CFG="${CMAKE_CURRENT_LIST_DIR}/detail/atomic_cfg.hpp")

if(${CMAKE_CXX_STANDARD} GREATER_EQUAL 20)
add_tests(FILES ct_format ct_string indexed_tuple tuple tuple_algorithms)
endif()
Expand Down
12 changes: 7 additions & 5 deletions test/atomic_bitset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ TEST_CASE("atomic_bitset with implicit storage element type",
static_assert(sizeof(stdx::atomic_bitset<64>) == sizeof(std::uint64_t));
}

TEMPLATE_TEST_CASE("atomic_bitset is always lock free", "[atomic_bitset]",
std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t) {
static_assert(stdx::atomic_bitset<8, TestType>::is_always_lock_free);
}

TEMPLATE_TEST_CASE("index operation", "[atomic_bitset]", std::uint8_t,
std::uint16_t, std::uint32_t, std::uint64_t) {
CHECK(not stdx::atomic_bitset<1, TestType>{}[0]);
Expand Down Expand Up @@ -163,6 +158,13 @@ TEMPLATE_TEST_CASE("convert to natural type", "[atomic_bitset]", std::uint8_t,
CHECK(bs.to_natural(std::memory_order_acquire) == 0b1000'1000u);
}

TEST_CASE("to_natural returns smallest_uint", "[atomic_bitset]") {
auto bs = stdx::atomic_bitset<4>{stdx::all_bits};
auto value = bs.to_natural();
CHECK(value == 0b1111);
static_assert(std::same_as<decltype(value), std::uint8_t>);
}

TEMPLATE_TEST_CASE("construct with a string_view", "[atomic_bitset]",
std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t) {
using namespace std::string_view_literals;
Expand Down
20 changes: 20 additions & 0 deletions test/atomic_bitset_override.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <stdx/atomic_bitset.hpp>

#include <catch2/catch_test_macros.hpp>

#include <concepts>
#include <cstdint>

TEST_CASE("atomic_bitset works with overridden type",
"[atomic_bitset_override]") {
auto bs = stdx::atomic_bitset<4>{};
static_assert(sizeof(decltype(bs)) == sizeof(std::uint32_t));
static_assert(alignof(decltype(bs)) == alignof(std::uint32_t));
}

TEST_CASE("to_natural returns smallest_uint", "[atomic_bitset_override]") {
auto bs = stdx::atomic_bitset<4>{stdx::all_bits};
auto value = bs.to_natural();
CHECK(value == 0b1111);
static_assert(std::same_as<decltype(value), std::uint8_t>);
}
7 changes: 7 additions & 0 deletions test/detail/atomic_cfg.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

#include <cstdint>
#include <type_traits>

template <>
struct atomic::atomic_type<std::uint8_t> : std::type_identity<std::uint32_t> {};
Loading