blob: 575d113cf441b992e74fdd4e6472894134b7b21c [file] [log] [blame]
#ifndef AOS_IPC_LIB_INDEX_H_
#define AOS_IPC_LIB_INDEX_H_
#include <stdint.h>
#include <limits>
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "aos/ipc_lib/shm_observers.h"
#ifndef AOS_QUEUE_ATOMIC_SIZE
#if UINTPTR_MAX == 0xffffffff
#define AOS_QUEUE_ATOMIC_SIZE 32
/* 32-bit */
#elif UINTPTR_MAX == 0xffffffffffffffff
#define AOS_QUEUE_ATOMIC_SIZE 64
/* 64-bit */
#else
#error "Unknown pointer size"
#endif
#endif
namespace aos::ipc_lib {
namespace testing {
class QueueIndexTest;
} // namespace testing
// There are 2 types of indices in the queue. 1 is the index into the overall
// queue. If we have sent 1,000,005 messages, this is that number.
//
// The other is the index into the message list. This is essentially a message
// pointer. It has some of the lower bits of the queue index encoded into it as
// a safeguard to detect if someone re-used a message out from under us and we
// couldn't tell otherwise. It is used to map a queue index to a message index.
//
// Each of these index types has an atomic version and a non-atomic. The atomic
// version is a wrapper around a uint32_t to hang helper functions off of.
// The non-atomic version contains all the logic. These are all in the header
// file to encourage the compiler to inline aggressively.
//
// The user should very infrequently be manipulating the values of these
// directly. Use the classes instead to do the heavy lifting.
// Structure for holding the index into the queue.
class QueueIndex {
public:
#if AOS_QUEUE_ATOMIC_SIZE == 64
typedef uint32_t PackedIndexType;
#else
typedef uint16_t PackedIndexType;
#endif
// Returns an invalid queue element which uses a reserved value.
static QueueIndex Invalid() { return QueueIndex(sentinal_value(), 0); }
// Returns a queue element pointing to 0.
static QueueIndex Zero(uint32_t count) { return QueueIndex(0, count); }
// Returns true if the index is valid.
bool valid() const { return index_ != sentinal_value(); }
// Returns the modulo base used to wrap to avoid overlapping with the reserved
// number.
// max_value is one more than the max value we can store.
// count is the the number of elements in the queue.
static constexpr uint32_t MaxIndex(uint32_t max_value, uint32_t count) {
return (max_value / count) * count;
}
// Gets the next index.
QueueIndex Increment() const { return IncrementBy(1u); }
// Gets the nth next element.
QueueIndex IncrementBy(uint32_t amount) const {
uint32_t index = index_ + amount;
uint32_t max_index = MaxIndex(sentinal_value(), count_);
if (index < index_) {
// We wrapped. We are shifting up by 0x100000000 - MaxIndex(...).
// Which is equivalent to subtracting MaxIndex since everything is modular
// with a uint32_t.
index -= max_index;
}
// Now, wrap the remainder.
index = index % max_index;
return QueueIndex(index, count_);
}
// Gets the nth previous element.
QueueIndex DecrementBy(uint32_t amount) const {
uint32_t index = index_ - amount;
if (index > index_) {
// We wrapped. We are shifting down by 0x100000000 - MaxIndex(...).
// Which is equivalent to adding MaxIndex since everything is modular with
// a uint32_t.
index += MaxIndex(sentinal_value(), count_);
}
return QueueIndex(index, count_);
}
// Returns true if the lowest bits of the queue index from the Index could
// plausibly match this queue index. The number of bits matched depends on
// the the size of atomics in use.
bool IsPlausible(PackedIndexType queue_index) const {
return valid() &&
(queue_index ==
static_cast<PackedIndexType>(
index_ & std::numeric_limits<PackedIndexType>::max()));
}
bool operator==(const QueueIndex other) const {
return other.index_ == index_;
}
bool operator!=(const QueueIndex other) const {
return other.index_ != index_;
}
// Returns the wrapped index into the queue.
uint32_t Wrapped() const { return index_ % count_; }
// Returns the raw index. This should be used very sparingly.
uint32_t index() const { return index_; }
QueueIndex Clear() const { return QueueIndex(0, count_); }
// Returns a string representing the index.
::std::string DebugString() const;
private:
QueueIndex(uint32_t index, uint32_t count) : index_(index), count_(count) {}
static constexpr uint32_t sentinal_value() { return 0xffffffffu; }
friend struct AtomicQueueIndex;
friend class Index;
// For testing.
friend class testing::QueueIndexTest;
// Index and number of elements in the queue.
uint32_t index_;
// Count is stored here rather than passed in everywhere in the hopes that the
// compiler completely optimizes out this class and this variable if it isn't
// used.
uint32_t count_;
};
// Atomic storage for setting and getting QueueIndex objects.
// Count is the number of messages in the queue.
struct AtomicQueueIndex {
public:
// Atomically reads the index without any ordering constraints.
QueueIndex RelaxedLoad(uint32_t count) const {
return QueueIndex(index_.load(::std::memory_order_relaxed), count);
}
// Full bidirectional barriers here.
QueueIndex Load(uint32_t count) const {
return QueueIndex(index_.load(::std::memory_order_acquire), count);
}
inline void Store(QueueIndex value) {
index_.store(value.index_, ::std::memory_order_release);
}
// Invalidates the element unconditionally.
inline void Invalidate() { Store(QueueIndex::Invalid()); }
inline void RelaxedInvalidate() {
index_.store(QueueIndex::Invalid().index_, ::std::memory_order_relaxed);
}
// Swaps expected for index atomically. Returns true on success, false
// otherwise.
inline bool CompareAndExchangeStrong(QueueIndex expected, QueueIndex index) {
linux_code::ipc_lib::RunShmObservers run_observers(&index_, true);
return index_.compare_exchange_strong(expected.index_, index.index_,
::std::memory_order_acq_rel);
}
private:
::std::atomic<uint32_t> index_;
};
// Structure holding the queue index and the index into the message list.
class Index {
public:
#if AOS_QUEUE_ATOMIC_SIZE == 64
typedef uint64_t IndexType;
typedef uint32_t MessageIndexType;
#else
typedef uint32_t IndexType;
typedef uint16_t MessageIndexType;
#endif
typedef QueueIndex::PackedIndexType PackedIndexType;
// Constructs an Index. queue_index is the QueueIndex of this message, and
// message_index is the index into the messages structure.
Index(QueueIndex queue_index, MessageIndexType message_index)
: Index(queue_index.index_, message_index) {}
Index(uint32_t queue_index, MessageIndexType message_index)
: index_(static_cast<IndexType>(
queue_index & std::numeric_limits<PackedIndexType>::max()) |
(static_cast<IndexType>(message_index)
<< std::numeric_limits<PackedIndexType>::digits)) {
CHECK_LE(message_index, MaxMessages());
}
// Index of this message in the message array.
MessageIndexType message_index() const {
return (index_ >> std::numeric_limits<PackedIndexType>::digits) &
std::numeric_limits<MessageIndexType>::max();
}
// Lowest bits of the queue index of this message in the queue. This will
// either be 16 or 32 bits, depending on if we have 32 or 64 bit atomics under
// the cover.
PackedIndexType queue_index() const {
return index_ & std::numeric_limits<PackedIndexType>::max();
}
// Returns true if the provided queue index plausibly represents this Index.
bool IsPlausible(QueueIndex queue_index) const {
return queue_index.IsPlausible(this->queue_index());
}
// Returns an invalid Index.
static Index Invalid() { return Index(sentinal_value()); }
// Checks if this Index is valid or not.
bool valid() const { return index_ != sentinal_value(); }
// Returns the raw Index. This should only be used for debug.
IndexType get() const { return index_; }
// Returns the maximum number of messages we can store before overflowing.
static constexpr MessageIndexType MaxMessages() {
return std::numeric_limits<MessageIndexType>::max() - 1;
}
bool operator==(const Index other) const { return other.index_ == index_; }
bool operator!=(const Index other) const { return other.index_ != index_; }
// Returns a string representing the index.
std::string DebugString() const;
private:
Index(IndexType index) : index_(index) {}
friend class AtomicIndex;
static constexpr IndexType sentinal_value() {
return std::numeric_limits<IndexType>::max();
}
// Note: a value of all 1 bits is a sentinal to represent an invalid entry.
// This works because we would need to have a queue index of 0x*ffff, *and*
// have 0xffff messages in the message list. That constraint is easy to
// enforce by limiting the max messages.
IndexType index_;
};
// Atomic storage for setting and getting Index objects.
class AtomicIndex {
public:
// Stores and loads atomically without ordering constraints.
Index RelaxedLoad() const {
return Index(index_.load(::std::memory_order_relaxed));
}
void RelaxedStore(Index index) {
index_.store(index.index_, ::std::memory_order_relaxed);
}
// Invalidates the index atomically, but without any ordering constraints.
void RelaxedInvalidate() { RelaxedStore(Index::Invalid()); }
// Full barriers here.
void Invalidate() { Store(Index::Invalid()); }
void Store(Index index) {
index_.store(index.index_, ::std::memory_order_release);
}
Index Load() const { return Index(index_.load(::std::memory_order_acquire)); }
// Swaps expected for index atomically. Returns true on success, false
// otherwise.
bool CompareAndExchangeStrong(Index expected, Index index) {
linux_code::ipc_lib::RunShmObservers run_observers(&index_, true);
return index_.compare_exchange_strong(expected.index_, index.index_,
::std::memory_order_acq_rel);
}
bool CompareAndExchangeWeak(Index *expected, Index index) {
return index_.compare_exchange_weak(expected->index_, index.index_,
::std::memory_order_acq_rel);
}
private:
::std::atomic<Index::IndexType> index_;
};
} // namespace aos::ipc_lib
#endif // AOS_IPC_LIB_INDEX_H_