properly byteswap r13 for spinlock Add PPCOpcodeBits stub out broken fpscr updating in ppc_hir_builder. it's just code that repeatedly does nothing right now. add note about 0 opcode bytes being executed to ppc_frontend Add assert to check that function end is greater than function start, can happen with malformed functions Disable prefetch and cachecontrol by default, automatic hardware prefetchers already do the job for the most part minor cleanup in simplification_pass, dont loop optimizations, let the pass manager do it for us Add experimental "delay_via_maybeyield" cvar, which uses MaybeYield to "emulate" the db16cyc instruction Add much faster/simpler way of directly calling guest functions, no longer have to do a byte by byte search through the generated code Generate label string ids on the fly Fix unused function warnings for prefetch on clang, fix many other clang warnings Eliminated majority of CallNativeSafes by replacing them with naive generic code paths. ^ Vector rotate left, vector shift left, vector shift right, vector shift arithmetic right, and vector average are included These naive paths are implemented small loops that stash the two inputs to the stack and load them in gprs from there, they are not particularly fast but should be an order of magnitude faster than callnativesafe to a host function, which would involve a call, stashing all volatile registers, an indirect call, potentially setting up a stack frame for the arrays that the inputs get stashed to, the actual operations, a return, loading all volatile registers, a return, etc Added the fast SHR_V128 path back in Implement signed vector average byte, signed vector average word. previously we were emitting no code for them. signed vector average byte appears in many games Fix bug with signed vector average 32, we were doing unsigned shift, turning negative values into big positive ones potentially
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_
|