blob: 8c3c999d853778bd9a3fabc7d08146b4a5977527 [file] [log] [blame] [edit]
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef NB_ATOMIC_H_
#define NB_ATOMIC_H_
#include "starboard/atomic.h"
#include "starboard/mutex.h"
#include "starboard/types.h"
namespace nb {
// Provides atomic types like integer and float. Some types like atomic_int32_t
// are likely to be hardware accelerated for your platform.
//
// Never use the parent types like atomic<T>, atomic_number<T> or
// atomic_integral<T> and instead use the fully qualified classes like
// atomic_int32_t, atomic_pointer<T*>, etc.
//
// Note on template instantiation, avoid using the parent type and instead
// use the fully qualified type.
// BAD:
// template<typename T>
// void Foo(const atomic<T>& value);
// GOOD:
// template<typename atomic_t>
// void Foo(const atomic_t& vlaue);
// Atomic Pointer class. Instantiate as atomic_pointer<void*>
// for void* pointer types.
template<typename T>
class atomic_pointer;
// Atomic bool class.
class atomic_bool;
// Atomic int32 class
class atomic_int32_t;
// Atomic int64 class.
class atomic_int64_t;
// Atomic float class.
class atomic_float;
// Atomic double class.
class atomic_double;
///////////////////////////////////////////////////////////////////////////////
// Class hiearchy.
///////////////////////////////////////////////////////////////////////////////
// Base functionality for atomic types. Defines exchange(), load(),
// store(), compare_exhange_weak(), compare_exchange_strong()
template <typename T>
class atomic;
// Subtype of atomic<T> for numbers likes float and integer types but not bool.
// Adds fetch_add() and fetch_sub().
template <typename T>
class atomic_number;
// Subtype of atomic_number<T> for integer types like int32 and int64. Adds
// increment and decrement.
template <typename T>
class atomic_integral;
///////////////////////////////////////////////////////////////////////////////
// Implimentation.
///////////////////////////////////////////////////////////////////////////////
// Similar to C++11 std::atomic<T>.
// atomic<T> may be instantiated with any TriviallyCopyable type T.
// atomic<T> is neither copyable nor movable.
template <typename T>
class atomic {
public:
typedef T ValueType;
// C++11 forbids a copy constructor for std::atomic<T>, it also forbids
// a move operation.
atomic() : value_() {}
explicit atomic(T v) : value_(v) {}
// Checks whether the atomic operations on all objects of this type
// are lock-free.
// Returns true if the atomic operations on the objects of this type
// are lock-free, false otherwise.
//
// All atomic types may be implemented using mutexes or other locking
// operations, rather than using the lock-free atomic CPU instructions.
// atomic types are also allowed to be sometimes lock-free, e.g. if only
// aligned memory accesses are naturally atomic on a given architecture,
// misaligned objects of the same type have to use locks.
//
// See also std::atomic<T>::is_lock_free().
bool is_lock_free() const { return false; }
bool is_lock_free() const volatile { return false; }
// Atomically replaces the value of the atomic object
// and returns the value held previously.
// See also std::atomic<T>::exchange().
T exchange(T new_val) {
T old_value;
{
starboard::ScopedLock lock(mutex_);
old_value = value_;
value_ = new_val;
}
return old_value;
}
// Atomically obtains the value of the atomic object.
// See also std::atomic<T>::load().
T load() const {
starboard::ScopedLock lock(mutex_);
return value_;
}
// Stores the value. See std::atomic<T>::store(...)
void store(T val) {
starboard::ScopedLock lock(mutex_);
value_ = val;
}
// compare_exchange_strong(...) sets the new value if and only if
// *expected_value matches what is stored internally.
// If this succeeds then true is returned and *expected_value == new_value.
// Otherwise If there is a mismatch then false is returned and
// *expected_value is set to the internal value.
// Inputs:
// new_value: Attempt to set the value to this new value.
// expected_value: A test condition for success. If the actual value
// matches the expected_value then the swap will succeed.
//
// See also std::atomic<T>::compare_exchange_strong(...).
bool compare_exchange_strong(T* expected_value, T new_value) {
// Save original value so that its local. This hints to the compiler
// that test_val doesn't have aliasing issues and should result in
// more optimal code inside of the lock.
const T test_val = *expected_value;
starboard::ScopedLock lock(mutex_);
if (test_val == value_) {
value_ = new_value;
return true;
} else {
*expected_value = value_;
return false;
}
}
// Weak version of this function is documented to be faster, but has allows
// weaker memory ordering and therefore will sometimes have a false negative:
// The value compared will actually be equal but the return value from this
// function indicates otherwise.
// By default, the function delegates to compare_exchange_strong(...).
//
// See also std::atomic<T>::compare_exchange_weak(...).
bool compare_exchange_weak(T* expected_value, T new_value) {
return compare_exchange_strong(expected_value, new_value);
}
protected:
T value_;
starboard::Mutex mutex_;
};
// A subclass of atomic<T> that adds fetch_add(...) and fetch_sub(...).
template <typename T>
class atomic_number : public atomic<T> {
public:
typedef atomic<T> Super;
typedef T ValueType;
atomic_number() : Super() {}
explicit atomic_number(T v) : Super(v) {}
// Returns the previous value before the input value was added.
// See also std::atomic<T>::fetch_add(...).
T fetch_add(T val) {
T old_val;
{
starboard::ScopedLock lock(this->mutex_);
old_val = this->value_;
this->value_ += val;
}
return old_val;
}
// Returns the value before the operation was applied.
// See also std::atomic<T>::fetch_sub(...).
T fetch_sub(T val) {
T old_val;
{
starboard::ScopedLock lock(this->mutex_);
old_val = this->value_;
this->value_ -= val;
}
return old_val;
}
};
// A subclass to classify Atomic Integers. Adds increment and decrement
// functions.
template <typename T>
class atomic_integral : public atomic_number<T> {
public:
typedef atomic_number<T> Super;
atomic_integral() : Super() {}
explicit atomic_integral(T v) : Super(v) {}
T increment() { return this->fetch_add(T(1)); }
T decrement() { return this->fetch_sub(T(1)); }
};
// atomic_pointer class.
template <typename T>
class atomic_pointer : public atomic<T> {
public:
typedef atomic<T> Super;
atomic_pointer() : Super() {}
explicit atomic_pointer(T initial_val) : Super(initial_val) {}
};
// Simple atomic bool class. This could be optimized for speed using
// compiler intrinsics for concurrent integer modification.
class atomic_bool : public atomic<bool> {
public:
typedef atomic<bool> Super;
atomic_bool() : Super() {}
explicit atomic_bool(bool initial_val) : Super(initial_val) {}
};
// Lockfree atomic int class.
class atomic_int32_t {
public:
typedef int32_t ValueType;
atomic_int32_t() : value_(0) {}
atomic_int32_t(SbAtomic32 value) : value_(value) {}
bool is_lock_free() const { return true; }
bool is_lock_free() const volatile { return true; }
int32_t increment() {
return fetch_add(1);
}
int32_t decrement() {
return fetch_add(-1);
}
int32_t fetch_add(int32_t val) {
// fetch_add is a post-increment operation, while SbAtomicBarrier_Increment
// is a pre-increment operation. Therefore subtract the value to match
// the expected interface.
return SbAtomicBarrier_Increment(volatile_ptr(), val) - val;
}
int32_t fetch_sub(int32_t val) {
return fetch_add(-val);
}
// Atomically replaces the value of the atomic object
// and returns the value held previously.
// See also std::atomic<T>::exchange().
int32_t exchange(int32_t new_val) {
return SbAtomicNoBarrier_Exchange(volatile_ptr(), new_val);
}
// Atomically obtains the value of the atomic object.
// See also std::atomic<T>::load().
int32_t load() const {
return SbAtomicAcquire_Load(volatile_const_ptr());
}
// Stores the value. See std::atomic<T>::store(...)
void store(int32_t val) {
SbAtomicRelease_Store(volatile_ptr(), val);
}
bool compare_exchange_strong(int32_t* expected_value, int32_t new_value) {
int32_t prev_value = *expected_value;
SbAtomicMemoryBarrier();
int32_t value_written = SbAtomicRelease_CompareAndSwap(volatile_ptr(),
prev_value,
new_value);
const bool write_ok = (prev_value == value_written);
if (!write_ok) {
*expected_value = value_written;
}
return write_ok;
}
// Weak version of this function is documented to be faster, but has allows
// weaker memory ordering and therefore will sometimes have a false negative:
// The value compared will actually be equal but the return value from this
// function indicates otherwise.
// By default, the function delegates to compare_exchange_strong(...).
//
// See also std::atomic<T>::compare_exchange_weak(...).
bool compare_exchange_weak(int32_t* expected_value, int32_t new_value) {
return compare_exchange_strong(expected_value, new_value);
}
private:
volatile int32_t* volatile_ptr() { return &value_; }
volatile const int32_t* volatile_const_ptr() const { return &value_; }
int32_t value_;
};
// Simple atomic int class. This could be optimized for speed using
// compiler intrinsics for concurrent integer modification.
class atomic_int64_t : public atomic_integral<int64_t> {
public:
typedef atomic_integral<int64_t> Super;
atomic_int64_t() : Super() {}
explicit atomic_int64_t(int64_t initial_val) : Super(initial_val) {}
};
class atomic_float : public atomic_number<float> {
public:
typedef atomic_number<float> Super;
atomic_float() : Super() {}
explicit atomic_float(float initial_val) : Super(initial_val) {}
};
class atomic_double : public atomic_number<double> {
public:
typedef atomic_number<double> Super;
atomic_double() : Super() {}
explicit atomic_double(double initial_val) : Super(initial_val) {}
};
} // namespace nb
#endif // NB_ATOMIC_H_