Skip to content

Commit

Permalink
Added atomic bit test data structure
Browse files Browse the repository at this point in the history
  • Loading branch information
shuhaowu committed Jul 13, 2024
1 parent eff5a1c commit fce49c8
Show file tree
Hide file tree
Showing 3 changed files with 290 additions and 0 deletions.
54 changes: 54 additions & 0 deletions include/cactus_rt/experimental/lockless/atomic_bitset.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#ifndef CACTUS_RT_EXPERIMENTAL_LOCKLESS_ATOMIC_BITSET_H_
#define CACTUS_RT_EXPERIMENTAL_LOCKLESS_ATOMIC_BITSET_H_

#include <atomic>
#include <cstddef>
#include <initializer_list>
#include <limits>

namespace cactus_rt::experimental::lockless {

template <typename T>
class AtomicBitset {
static_assert(std::atomic<T>::is_always_lock_free);
std::atomic<T> data_;

// Avoid any casting that might occur during bit shifting later.
static constexpr T kOne = 1;

public:
static constexpr size_t kCapacity = std::numeric_limits<T>::digits;

/**
* Always initialize the bitset to be 0 at the start.
*/
AtomicBitset() : data_(0) {}

void Set(const size_t i, const std::memory_order order = std::memory_order_seq_cst);

void SetRange(const std::initializer_list<size_t> indices, const std::memory_order order = std::memory_order_seq_cst);

void Reset(const size_t i, const std::memory_order order = std::memory_order_seq_cst);

void ResetRange(const std::initializer_list<size_t> indices, const std::memory_order order = std::memory_order_seq_cst);

void Flip(const size_t t, const std::memory_order order = std::memory_order_seq_cst);

void FlipRange(const std::initializer_list<size_t> indices, const std::memory_order order = std::memory_order_seq_cst);

void SetValue(const size_t i, const bool value, const std::memory_order order = std::memory_order_seq_cst);

bool Test(const size_t i, const std::memory_order order = std::memory_order_seq_cst) const;

T Value(const std::memory_order order = std::memory_order_seq_cst) const {
return data_.load(order);
}

bool operator[](const size_t i) const {
return Test(i);
}
};

} // namespace cactus_rt::experimental::lockless

#endif
100 changes: 100 additions & 0 deletions src/cactus_rt/experimental/lockless/atomic_bitset.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#include "cactus_rt/experimental/lockless/atomic_bitset.h"

#include <cassert> // TODO: switch to some sort of bounded integer arrangement.

namespace cactus_rt::experimental::lockless {
template <typename T>
void AtomicBitset<T>::Set(const size_t i, const std::memory_order order) {
assert(i < kCapacity);

T bitmask = kOne << i;
data_.fetch_or(bitmask, order);
}

template <typename T>
void AtomicBitset<T>::SetRange(const std::initializer_list<size_t> indices, const std::memory_order order) {
T bitmask = 0;
for (const auto i : indices) {
bitmask |= (kOne << i);
}

data_.fetch_or(bitmask, order);
}

template <typename T>
void AtomicBitset<T>::Reset(const size_t i, const std::memory_order order) {
assert(i < kCapacity);

T bitmask = ~(kOne << i);
data_.fetch_and(bitmask, order);
}

template <typename T>
void AtomicBitset<T>::ResetRange(const std::initializer_list<size_t> indices, const std::memory_order order) {
T bitmask = 0;
for (const auto i : indices) {
bitmask |= (kOne << i);
}

bitmask = ~(bitmask);
data_.fetch_and(bitmask, order);
}

template <typename T>
void AtomicBitset<T>::Flip(const size_t i, const std::memory_order order) {
assert(i < kCapacity);

T bitmask = kOne << i;
data_.fetch_xor(bitmask, order);
}

template <typename T>
void AtomicBitset<T>::FlipRange(const std::initializer_list<size_t> indices, const std::memory_order order) {
T bitmask = 0;
for (const auto i : indices) {
bitmask |= (kOne << i);
}

data_.fetch_xor(bitmask, order);
}

template <typename T>
void AtomicBitset<T>::SetValue(const size_t i, const bool value, const std::memory_order order) {
assert(i < kCapacity);

if (value) {
Set(i, order);
} else {
Reset(i, order);
}
}

template <typename T>
bool AtomicBitset<T>::Test(const size_t i, const std::memory_order order) const {
assert(i < kCapacity);

T bitmask = kOne << i;
return data_.load(order) & bitmask;
}

#if (ATOMIC_LLONG_LOCK_FREE == 2)
template class AtomicBitset<unsigned long long>;
#endif

#if (ATOMIC_LONG_LOCK_FREE == 2)
template class AtomicBitset<unsigned long>;
#endif

#if (ATOMIC_INT_LOCK_FREE == 2)
template class AtomicBitset<unsigned int>;
#endif

#if (ATOMIC_SHORT_LOCK_FREE == 2)
template class AtomicBitset<unsigned short>;
#endif

#if (ATOMIC_CHAR_LOCK_FREE == 2)
template class AtomicBitset<unsigned char>;
#endif

} // namespace cactus_rt::experimental::lockless
136 changes: 136 additions & 0 deletions tests/experimental/lockless/atomic_bitset_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#include "cactus_rt/experimental/lockless/atomic_bitset.h"

#include <gtest/gtest.h>

using AtomicBitset8 = cactus_rt::experimental::lockless::AtomicBitset<uint8_t>;
using AtomicBitset64 = cactus_rt::experimental::lockless::AtomicBitset<uint64_t>;

TEST(AtomicBitsetTest, SetResetAndLoad) {
AtomicBitset8 bitset8;

for (size_t i = 0; i < 8; i++) {
EXPECT_FALSE(bitset8.Test(i));
}

bitset8.Set(0);

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8.Test(i), i == 0);
}

bitset8.Set(2);

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8.Test(i), i == 0 || i == 2);
}

bitset8.Set(7);

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8.Test(i), i == 0 || i == 2 || i == 7);
}

bitset8.Reset(7);

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8[i], i == 0 || i == 2);
}

bitset8.Reset(6);

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8.Test(i), i == 0 || i == 2);
}

bitset8.Reset(0);

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8.Test(i), i == 2);
}
}

TEST(AtomicBitsetTest, SetRangeResetRangeAndTest) {
AtomicBitset8 bitset8;

bitset8.SetRange({1, 6});

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8.Test(i), (i == 1) || (i == 6));
}

bitset8.ResetRange({1, 2});

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8.Test(i), i == 6);
}

bitset8.SetRange({2, 6});

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8.Test(i), (i == 2) || (i == 6));
}

bitset8.ResetRange({1, 2, 6});

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8.Test(i), false);
}
}

TEST(AtomicBitsetTest, FlipAndTest) {
AtomicBitset8 bitset8;

bitset8.Flip(2);

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8.Test(i), i == 2);
}

bitset8.FlipRange({2, 3, 7});

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8[i], (i == 3) || (i == 7));
}

bitset8.Flip(2);

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8[i], (i == 2) || (i == 3) || (i == 7));
}
}

TEST(AtomicBitsetTest, SetValueAndTest) {
AtomicBitset8 bitset8;

bitset8.SetValue(2, true);

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8.Test(i), i == 2);
}

bitset8.SetValue(2, false);

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8[i], false);
}

bitset8.SetValue(2, false);

for (size_t i = 0; i < 8; i++) {
EXPECT_EQ(bitset8[i], false);
}
}

TEST(AtomicBitsetTest, OutOfRange) {
AtomicBitset8 bitset8;
EXPECT_DEATH(bitset8.Set(8), "");

AtomicBitset64 bitset64;
bitset64.Set(8);

for (size_t i = 0; i < 64; i++) {
EXPECT_EQ(bitset64[i], i == 8);
}

EXPECT_DEATH(bitset8.Set(64), "");
}

0 comments on commit fce49c8

Please sign in to comment.