// Copyright 2014 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "src/base/atomic-utils.h" #include "src/base/platform/platform.h" #include "testing/gtest/include/gtest/gtest.h" namespace v8 { namespace base { TEST(AtomicNumber, Constructor) { // Test some common types. AtomicNumber zero_int; AtomicNumber zero_size_t; AtomicNumber zero_intptr_t; EXPECT_EQ(0, zero_int.Value()); EXPECT_EQ(0u, zero_size_t.Value()); EXPECT_EQ(0, zero_intptr_t.Value()); } TEST(AtomicNumber, Value) { AtomicNumber a(1); EXPECT_EQ(1, a.Value()); AtomicNumber b(-1); EXPECT_EQ(-1, b.Value()); AtomicNumber c(1); EXPECT_EQ(1u, c.Value()); AtomicNumber d(static_cast(-1)); EXPECT_EQ(std::numeric_limits::max(), d.Value()); } TEST(AtomicNumber, SetValue) { AtomicNumber a(1); a.SetValue(-1); EXPECT_EQ(-1, a.Value()); } TEST(AtomicNumber, Increment) { AtomicNumber a(std::numeric_limits::max()); a.Increment(1); EXPECT_EQ(std::numeric_limits::min(), a.Value()); // Check that potential signed-ness of the underlying storage has no impact // on unsigned types. AtomicNumber b(std::numeric_limits::max()); b.Increment(1); EXPECT_EQ(static_cast(std::numeric_limits::max()) + 1, b.Value()); // Should work as decrement as well. AtomicNumber c(1); c.Increment(-1); EXPECT_EQ(0u, c.Value()); c.Increment(-1); EXPECT_EQ(std::numeric_limits::max(), c.Value()); } TEST(AtomicNumber, Decrement) { AtomicNumber a(std::numeric_limits::max()); a.Increment(1); EXPECT_EQ(0u, a.Value()); a.Decrement(1); EXPECT_EQ(std::numeric_limits::max(), a.Value()); } TEST(AtomicNumber, OperatorAdditionAssignment) { AtomicNumber a(0u); AtomicNumber b(std::numeric_limits::max()); a += b.Value(); EXPECT_EQ(a.Value(), b.Value()); EXPECT_EQ(b.Value(), std::numeric_limits::max()); } TEST(AtomicNumber, OperatorSubtractionAssignment) { AtomicNumber a(std::numeric_limits::max()); AtomicNumber b(std::numeric_limits::max()); a -= b.Value(); EXPECT_EQ(a.Value(), 0u); EXPECT_EQ(b.Value(), std::numeric_limits::max()); } namespace { enum TestFlag : base::AtomicWord { kA, kB, kC, }; } // namespace TEST(AtomicValue, Initial) { AtomicValue a(kA); EXPECT_EQ(TestFlag::kA, a.Value()); } TEST(AtomicValue, TrySetValue) { AtomicValue a(kA); EXPECT_FALSE(a.TrySetValue(kB, kC)); EXPECT_TRUE(a.TrySetValue(kA, kC)); EXPECT_EQ(TestFlag::kC, a.Value()); } TEST(AtomicValue, SetValue) { AtomicValue a(kB); a.SetValue(kC); EXPECT_EQ(TestFlag::kC, a.Value()); } TEST(AtomicValue, WithVoidStar) { AtomicValue a(nullptr); AtomicValue dummy(nullptr); EXPECT_EQ(nullptr, a.Value()); a.SetValue(&a); EXPECT_EQ(&a, a.Value()); EXPECT_FALSE(a.TrySetValue(nullptr, &dummy)); EXPECT_TRUE(a.TrySetValue(&a, &dummy)); 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])); } } TEST(AsAtomicWord, SetBits_Sequential) { uintptr_t word = 0; // Fill the word with a repeated 0xF0 pattern. for (unsigned i = 0; i < sizeof(word); i++) { word = (word << 8) | 0xF0; } // Check the pattern. for (unsigned i = 0; i < sizeof(word); i++) { EXPECT_EQ(0xF0u, (word >> (i * 8) & 0xFFu)); } // Set the i-th byte value to i. uintptr_t mask = 0xFF; for (unsigned i = 0; i < sizeof(word); i++) { uintptr_t byte = static_cast(i) << (i * 8); AsAtomicWord::SetBits(&word, byte, mask); mask <<= 8; } for (unsigned i = 0; i < sizeof(word); i++) { EXPECT_EQ(i, (word >> (i * 8) & 0xFFu)); } } namespace { class BitSettingThread final : public Thread { public: BitSettingThread() : Thread(Options("BitSettingThread")), word_addr_(nullptr), bit_index_(0) {} void Initialize(uintptr_t* word_addr, int bit_index) { word_addr_ = word_addr; bit_index_ = bit_index; } void Run() override { uintptr_t bit = 1; bit = bit << bit_index_; AsAtomicWord::SetBits(word_addr_, bit, bit); } private: uintptr_t* word_addr_; int bit_index_; }; } // namespace. TEST(AsAtomicWord, SetBits_Concurrent) { const int kBitCount = sizeof(uintptr_t) * 8; const int kThreadCount = kBitCount / 2; BitSettingThread threads[kThreadCount]; uintptr_t word; AsAtomicWord::Relaxed_Store(&word, 0); for (int i = 0; i < kThreadCount; i++) { // Thread i sets bit number i * 2. threads[i].Initialize(&word, i * 2); } for (int i = 0; i < kThreadCount; i++) { threads[i].Start(); } for (int i = 0; i < kThreadCount; i++) { threads[i].Join(); } uintptr_t actual_word = AsAtomicWord::Relaxed_Load(&word); for (int i = 0; i < kBitCount; i++) { // Every second bit must be set. uintptr_t expected = (i % 2 == 0); EXPECT_EQ(expected, actual_word & 1u); actual_word >>= 1; } } } // namespace base } // namespace v8