197 lines
6.5 KiB
C++
197 lines
6.5 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2015 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#ifndef XENIA_BASE_RING_BUFFER_H_
|
|
#define XENIA_BASE_RING_BUFFER_H_
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
#include "xenia/base/assert.h"
|
|
#include "xenia/base/byte_order.h"
|
|
#include "xenia/base/math.h"
|
|
#include "xenia/base/memory.h"
|
|
|
|
namespace xe {
|
|
/*
|
|
todo: this class is CRITICAL to the performance of the entire emulator
|
|
currently, about 0.74% cpu time is still taken up by ReadAndSwap, 0.23
|
|
is used by read_count I believe that part of the issue is that smaller
|
|
ringbuffers are kicking off an automatic prefetcher stream, that ends up
|
|
reading ahead of the end of the ring because it can only go in a straight
|
|
line it then gets a cache miss when it eventually wraps around to the start
|
|
of the ring? really hard to tell whats going on there honestly, maybe we can
|
|
occasionally prefetch the first line of the ring to L1? For the automatic
|
|
prefetching i don't think there are any good options. I don't know if we have
|
|
any control over where these buffers will be (they seem to be in guest memory
|
|
:/), but if we did we could right-justify the buffer so that the final byte
|
|
of the ring ends at the end of a page. i think most automatic prefetchers
|
|
cannot cross page boundaries it does feel like something isnt right here
|
|
though
|
|
|
|
todo: microoptimization, we can change our size members to be uint32 so
|
|
that the registers no longer need the rex prefix, shrinking the generated
|
|
code a bit.. like i said, every bit helps in this class
|
|
*/
|
|
using ring_size_t = uint32_t;
|
|
class RingBuffer {
|
|
public:
|
|
RingBuffer(uint8_t* buffer, size_t capacity);
|
|
|
|
uint8_t* buffer() const { return buffer_; }
|
|
ring_size_t capacity() const { return capacity_; }
|
|
bool empty() const { return read_offset_ == write_offset_; }
|
|
|
|
ring_size_t read_offset() const { return read_offset_; }
|
|
uintptr_t read_ptr() const {
|
|
return uintptr_t(buffer_) + static_cast<uintptr_t>(read_offset_);
|
|
}
|
|
|
|
// todo: offset/ capacity_ is probably always 1 when its over, just check and
|
|
// subtract instead
|
|
void set_read_offset(size_t offset) { read_offset_ = offset % capacity_; }
|
|
ring_size_t read_count() const {
|
|
// chrispy: these branches are unpredictable
|
|
|
|
ring_size_t read_offs = read_offset_;
|
|
ring_size_t write_offs = write_offset_;
|
|
ring_size_t cap = capacity_;
|
|
|
|
ring_size_t offset_delta = write_offs - read_offs;
|
|
ring_size_t wrap_read_count = (cap - read_offs) + write_offs;
|
|
|
|
|
|
if (XE_LIKELY(read_offs <= write_offs)) {
|
|
return offset_delta; // will be 0 if they are equal, semantically
|
|
// identical to old code (i checked the asm, msvc
|
|
// does not automatically do this)
|
|
} else {
|
|
return wrap_read_count;
|
|
}
|
|
}
|
|
|
|
ring_size_t write_offset() const { return write_offset_; }
|
|
uintptr_t write_ptr() const { return uintptr_t(buffer_) + write_offset_; }
|
|
void set_write_offset(size_t offset) {
|
|
write_offset_ = static_cast<ring_size_t>(offset) % capacity_;
|
|
}
|
|
ring_size_t write_count() const {
|
|
if (read_offset_ == write_offset_) {
|
|
return capacity_;
|
|
} else if (write_offset_ < read_offset_) {
|
|
return read_offset_ - write_offset_;
|
|
} else {
|
|
return (capacity_ - write_offset_) + read_offset_;
|
|
}
|
|
}
|
|
|
|
void AdvanceRead(size_t count);
|
|
void AdvanceWrite(size_t count);
|
|
|
|
struct ReadRange {
|
|
const uint8_t* XE_RESTRICT first;
|
|
|
|
const uint8_t* XE_RESTRICT second;
|
|
ring_size_t first_length;
|
|
ring_size_t second_length;
|
|
};
|
|
ReadRange BeginRead(size_t count);
|
|
void EndRead(ReadRange read_range);
|
|
|
|
/*
|
|
BeginRead, but if there is a second Range it will prefetch all lines of
|
|
it
|
|
|
|
this does not prefetch the first range, because software
|
|
prefetching can do that faster than we can
|
|
*/
|
|
template <swcache::PrefetchTag tag>
|
|
XE_FORCEINLINE ReadRange BeginPrefetchedRead(size_t count) {
|
|
ReadRange range = BeginRead(count);
|
|
|
|
if (range.second) {
|
|
ring_size_t numlines =
|
|
xe::align<ring_size_t>(range.second_length, XE_HOST_CACHE_LINE_SIZE) /
|
|
XE_HOST_CACHE_LINE_SIZE;
|
|
// chrispy: maybe unroll?
|
|
for (ring_size_t i = 0; i < numlines; ++i) {
|
|
swcache::Prefetch<tag>(range.second + (i * XE_HOST_CACHE_LINE_SIZE));
|
|
}
|
|
}
|
|
return range;
|
|
}
|
|
|
|
size_t Read(uint8_t* buffer, size_t count);
|
|
template <typename T>
|
|
size_t Read(T* buffer, size_t count) {
|
|
return Read(reinterpret_cast<uint8_t*>(buffer), count);
|
|
}
|
|
|
|
template <typename T>
|
|
T Read() {
|
|
static_assert(std::is_fundamental<T>::value,
|
|
"Immediate read only supports basic types!");
|
|
|
|
T imm;
|
|
size_t read = Read(reinterpret_cast<uint8_t*>(&imm), sizeof(T));
|
|
assert_true(read == sizeof(T));
|
|
return imm;
|
|
}
|
|
|
|
template <typename T>
|
|
T ReadAndSwap() {
|
|
static_assert(std::is_fundamental<T>::value,
|
|
"Immediate read only supports basic types!");
|
|
|
|
T imm;
|
|
size_t read = Read(reinterpret_cast<uint8_t*>(&imm), sizeof(T));
|
|
assert_true(read == sizeof(T));
|
|
imm = xe::byte_swap(imm);
|
|
return imm;
|
|
}
|
|
|
|
size_t Write(const uint8_t* buffer, size_t count);
|
|
template <typename T>
|
|
size_t Write(const T* buffer, size_t count) {
|
|
return Write(reinterpret_cast<const uint8_t*>(buffer), count);
|
|
}
|
|
|
|
template <typename T>
|
|
size_t Write(T& data) {
|
|
return Write(reinterpret_cast<const uint8_t*>(&data), sizeof(T));
|
|
}
|
|
|
|
private:
|
|
uint8_t* XE_RESTRICT buffer_ = nullptr;
|
|
ring_size_t capacity_ = 0;
|
|
ring_size_t read_offset_ = 0;
|
|
ring_size_t write_offset_ = 0;
|
|
};
|
|
|
|
template <>
|
|
inline uint32_t RingBuffer::ReadAndSwap<uint32_t>() {
|
|
ring_size_t read_offset = this->read_offset_;
|
|
xenia_assert(this->capacity_ >= 4);
|
|
|
|
ring_size_t next_read_offset = read_offset + 4;
|
|
|
|
if (XE_UNLIKELY(next_read_offset == this->capacity_)) {
|
|
next_read_offset = 0;
|
|
// todo: maybe prefetch next? or should that happen much earlier?
|
|
}
|
|
this->read_offset_ = next_read_offset;
|
|
uint32_t ring_value = *(uint32_t*)&this->buffer_[read_offset];
|
|
return xe::byte_swap(ring_value);
|
|
}
|
|
} // namespace xe
|
|
|
|
#endif // XENIA_BASE_RING_BUFFER_H_
|