[base] Introduce AsAtomic8 helper class.

This class provides byte level CAS operation using word level CAS.

Bug: chromium:694255
Change-Id: I39e661ee8d11e3f61fd5cb64c36f8f5ee94d1244
Reviewed-on: https://chromium-review.googlesource.com/612170
Commit-Queue: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47311}
This commit is contained in:
Ulan Degenbaev 2017-08-11 14:04:53 +02:00 committed by Commit Bot
parent 41462d5757
commit 470e8024de
2 changed files with 164 additions and 0 deletions

View File

@ -266,6 +266,88 @@ class AsAtomicWord {
}
};
class AsAtomic8 {
public:
template <typename T>
static T Acquire_Load(T* addr) {
STATIC_ASSERT(sizeof(T) <= sizeof(base::Atomic8));
return to_return_type<T>(base::Acquire_Load(to_storage_addr(addr)));
}
template <typename T>
static T Relaxed_Load(T* addr) {
STATIC_ASSERT(sizeof(T) <= sizeof(base::Atomic8));
return to_return_type<T>(base::Relaxed_Load(to_storage_addr(addr)));
}
template <typename T>
static void Release_Store(T* addr,
typename std::remove_reference<T>::type new_value) {
STATIC_ASSERT(sizeof(T) <= sizeof(base::Atomic8));
base::Release_Store(to_storage_addr(addr), to_storage_type(new_value));
}
template <typename T>
static void Relaxed_Store(T* addr,
typename std::remove_reference<T>::type new_value) {
STATIC_ASSERT(sizeof(T) <= sizeof(base::Atomic8));
base::Relaxed_Store(to_storage_addr(addr), to_storage_type(new_value));
}
template <typename T>
static T Release_CompareAndSwap(
T* byte_addr, typename std::remove_reference<T>::type old_value,
typename std::remove_reference<T>::type new_value) {
STATIC_ASSERT(sizeof(T) <= sizeof(base::Atomic8));
// Since there is no byte level CAS, we use word level CAS. For that we
// need to find the word containing the byte and do a CAS loop that re-
// tries if unrelated bits of the word change concurrently.
const uintptr_t kAlignmentSize = sizeof(base::AtomicWord);
const uintptr_t kAlignmentMask = kAlignmentSize - 1;
uintptr_t word_addr =
reinterpret_cast<uintptr_t>(byte_addr) & ~kAlignmentMask;
uintptr_t byte_offset =
reinterpret_cast<uintptr_t>(byte_addr) & kAlignmentMask;
#if defined(V8_TARGET_BIG_ENDIAN)
uintptr_t byte_shift = (kAlignmentSize - 1 - byte_offset) * 8u;
#else
uintptr_t byte_shift = byte_offset * 8u;
#endif
uintptr_t byte_mask = static_cast<uintptr_t>(0xFFu) << byte_shift;
uintptr_t* addr = reinterpret_cast<uintptr_t*>(word_addr);
uintptr_t actual_word, new_word;
base::Atomic8 actual_byte;
do {
actual_word = base::AsAtomicWord::Relaxed_Load(addr);
actual_byte = to_storage_type((actual_word & byte_mask) >> byte_shift);
if (actual_byte != to_storage_type(old_value))
return to_return_type<T>(actual_byte);
uintptr_t new_byte = static_cast<uintptr_t>(new_value);
new_word = (actual_word & ~byte_mask) | (new_byte << byte_shift);
} while (base::AsAtomicWord::Release_CompareAndSwap(
addr, actual_word, new_word) != actual_word);
return old_value;
}
private:
template <typename T>
static base::Atomic8 to_storage_type(T value) {
return static_cast<base::Atomic8>(value);
}
template <typename T>
static T to_return_type(base::Atomic8 value) {
return static_cast<T>(value);
}
template <typename T>
static base::Atomic8* to_storage_addr(T* value) {
return reinterpret_cast<base::Atomic8*>(value);
}
template <typename T>
static const base::Atomic8* to_storage_addr(const T* value) {
return reinterpret_cast<const base::Atomic8*>(value);
}
};
class AsAtomicPointer {
public:
template <typename T>

View File

@ -5,6 +5,7 @@
#include <limits.h>
#include "src/base/atomic-utils.h"
#include "src/base/platform/platform.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
@ -125,5 +126,86 @@ TEST(AtomicValue, WithVoidStar) {
EXPECT_EQ(&dummy, a.Value());
}
TEST(AsAtomic8, CompareAndSwap_Sequential) {
uint8_t bytes[8];
for (int i = 0; i < 8; i++) {
bytes[i] = 0xF0 + i;
}
for (int i = 0; i < 8; i++) {
EXPECT_EQ(0xF0 + i,
AsAtomic8::Release_CompareAndSwap(&bytes[i], i, 0xF7 + i));
}
for (int i = 0; i < 8; i++) {
EXPECT_EQ(0xF0 + i,
AsAtomic8::Release_CompareAndSwap(&bytes[i], 0xF0 + i, 0xF7 + i));
}
for (int i = 0; i < 8; i++) {
EXPECT_EQ(0xF7 + i, bytes[i]);
}
}
namespace {
class ByteIncrementingThread final : public Thread {
public:
ByteIncrementingThread()
: Thread(Options("ByteIncrementingThread")),
byte_addr_(nullptr),
increments_(0) {}
void Initialize(uint8_t* byte_addr, int increments) {
byte_addr_ = byte_addr;
increments_ = increments;
}
void Run() override {
for (int i = 0; i < increments_; i++) {
Increment();
}
}
void Increment() {
uint8_t byte;
do {
byte = AsAtomic8::Relaxed_Load(byte_addr_);
} while (AsAtomic8::Release_CompareAndSwap(byte_addr_, byte, byte + 1) !=
byte);
}
private:
uint8_t* byte_addr_;
int increments_;
};
} // namespace
TEST(AsAtomic8, CompareAndSwap_Concurrent) {
const int kIncrements = 10;
const int kByteCount = 8;
uint8_t bytes[kByteCount];
const int kThreadsPerByte = 4;
const int kThreadCount = kByteCount * kThreadsPerByte;
ByteIncrementingThread threads[kThreadCount];
for (int i = 0; i < kByteCount; i++) {
AsAtomic8::Relaxed_Store(&bytes[i], i);
for (int j = 0; j < kThreadsPerByte; j++) {
threads[i * kThreadsPerByte + j].Initialize(&bytes[i], kIncrements);
}
}
for (int i = 0; i < kThreadCount; i++) {
threads[i].Start();
}
for (int i = 0; i < kThreadCount; i++) {
threads[i].Join();
}
for (int i = 0; i < kByteCount; i++) {
EXPECT_EQ(i + kIncrements * kThreadsPerByte,
AsAtomic8::Relaxed_Load(&bytes[i]));
}
}
} // namespace base
} // namespace v8