mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-25 20:22:30 +01:00
SPU/Non-TSX: Implement cuncurrent reservations
This commit is contained in:
parent
db24ce7708
commit
75ad56338b
@ -682,6 +682,7 @@ static atomic_t<u32> s_dummy_atomic = 0;
|
|||||||
bool cpu_thread::check_state() noexcept
|
bool cpu_thread::check_state() noexcept
|
||||||
{
|
{
|
||||||
bool cpu_sleep_called = false;
|
bool cpu_sleep_called = false;
|
||||||
|
bool cpu_memory_checked = false;
|
||||||
bool cpu_can_stop = true;
|
bool cpu_can_stop = true;
|
||||||
bool escape{}, retval{};
|
bool escape{}, retval{};
|
||||||
|
|
||||||
@ -770,7 +771,7 @@ bool cpu_thread::check_state() noexcept
|
|||||||
if (!is_stopped(flags) && flags.none_of(cpu_flag::ret))
|
if (!is_stopped(flags) && flags.none_of(cpu_flag::ret))
|
||||||
{
|
{
|
||||||
// Check pause flags which hold thread inside check_state (ignore suspend/debug flags on cpu_flag::temp)
|
// Check pause flags which hold thread inside check_state (ignore suspend/debug flags on cpu_flag::temp)
|
||||||
if (flags & (cpu_flag::pause + cpu_flag::memory) || (cpu_can_stop && flags & (cpu_flag::dbg_global_pause + cpu_flag::dbg_pause + cpu_flag::suspend + cpu_flag::yield + cpu_flag::preempt)))
|
if (flags & cpu_flag::pause || (!cpu_memory_checked && flags & cpu_flag::memory) || (cpu_can_stop && flags & (cpu_flag::dbg_global_pause + cpu_flag::dbg_pause + cpu_flag::suspend + cpu_flag::yield + cpu_flag::preempt)))
|
||||||
{
|
{
|
||||||
if (!(flags & cpu_flag::wait))
|
if (!(flags & cpu_flag::wait))
|
||||||
{
|
{
|
||||||
@ -789,12 +790,18 @@ bool cpu_thread::check_state() noexcept
|
|||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (flags & (cpu_flag::wait + cpu_flag::memory))
|
||||||
|
{
|
||||||
|
flags -= (cpu_flag::wait + cpu_flag::memory);
|
||||||
|
store = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (s_tls_thread_slot == umax)
|
if (s_tls_thread_slot == umax)
|
||||||
{
|
{
|
||||||
if (cpu_flag::wait - state)
|
if (cpu_flag::wait - this->state.load())
|
||||||
{
|
{
|
||||||
// Force wait flag (must be set during ownership of s_cpu_lock), this makes the atomic op fail as a side effect
|
// Force wait flag (must be set during ownership of s_cpu_lock), this makes the atomic op fail as a side effect
|
||||||
state += cpu_flag::wait;
|
this->state += cpu_flag::wait;
|
||||||
store = true;
|
store = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -802,12 +809,6 @@ bool cpu_thread::check_state() noexcept
|
|||||||
cpu_counter::add(this);
|
cpu_counter::add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags & cpu_flag::wait)
|
|
||||||
{
|
|
||||||
flags -= cpu_flag::wait;
|
|
||||||
store = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
retval = false;
|
retval = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -856,6 +857,14 @@ bool cpu_thread::check_state() noexcept
|
|||||||
|
|
||||||
if (escape)
|
if (escape)
|
||||||
{
|
{
|
||||||
|
if (vm::g_range_lock_bits[1] && vm::g_tls_locked && *vm::g_tls_locked == this)
|
||||||
|
{
|
||||||
|
state += cpu_flag::wait + cpu_flag::memory;
|
||||||
|
cpu_sleep_called = false;
|
||||||
|
cpu_memory_checked = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (cpu_can_stop && state0 & cpu_flag::pending)
|
if (cpu_can_stop && state0 & cpu_flag::pending)
|
||||||
{
|
{
|
||||||
// Execute pending work
|
// Execute pending work
|
||||||
@ -866,6 +875,7 @@ bool cpu_thread::check_state() noexcept
|
|||||||
// Work could have changed flags
|
// Work could have changed flags
|
||||||
// Reset internal flags as if check_state() has just been called
|
// Reset internal flags as if check_state() has just been called
|
||||||
cpu_sleep_called = false;
|
cpu_sleep_called = false;
|
||||||
|
cpu_memory_checked = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -883,6 +893,7 @@ bool cpu_thread::check_state() noexcept
|
|||||||
{
|
{
|
||||||
cpu_sleep();
|
cpu_sleep();
|
||||||
cpu_sleep_called = true;
|
cpu_sleep_called = true;
|
||||||
|
cpu_memory_checked = false;
|
||||||
|
|
||||||
if (s_tls_thread_slot != umax)
|
if (s_tls_thread_slot != umax)
|
||||||
{
|
{
|
||||||
@ -907,6 +918,7 @@ bool cpu_thread::check_state() noexcept
|
|||||||
if (state0 & cpu_flag::memory)
|
if (state0 & cpu_flag::memory)
|
||||||
{
|
{
|
||||||
vm::passive_lock(*this);
|
vm::passive_lock(*this);
|
||||||
|
cpu_memory_checked = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,6 +326,29 @@ extern thread_local u64 g_tls_fault_spu;
|
|||||||
|
|
||||||
const spu_decoder<spu_itype> s_spu_itype;
|
const spu_decoder<spu_itype> s_spu_itype;
|
||||||
|
|
||||||
|
namespace vm
|
||||||
|
{
|
||||||
|
extern atomic_t<u64, 64> g_range_lock_set[64];
|
||||||
|
|
||||||
|
// Defined here for performance reasons
|
||||||
|
writer_lock::~writer_lock() noexcept
|
||||||
|
{
|
||||||
|
if (range_lock)
|
||||||
|
{
|
||||||
|
if (!*range_lock)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_range_lock_bits[1] &= ~(1ull << (range_lock - g_range_lock_set));
|
||||||
|
range_lock->release(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_range_lock_bits[1].release(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace spu
|
namespace spu
|
||||||
{
|
{
|
||||||
namespace scheduler
|
namespace scheduler
|
||||||
@ -3548,19 +3571,18 @@ bool spu_thread::do_putllc(const spu_mfc_cmd& args)
|
|||||||
{
|
{
|
||||||
// Full lock (heavyweight)
|
// Full lock (heavyweight)
|
||||||
// TODO: vm::check_addr
|
// TODO: vm::check_addr
|
||||||
vm::writer_lock lock(addr);
|
vm::writer_lock lock(addr, range_lock);
|
||||||
|
|
||||||
if (cmp_rdata(rdata, super_data))
|
if (cmp_rdata(rdata, super_data))
|
||||||
{
|
{
|
||||||
mov_rdata(super_data, to_write);
|
mov_rdata(super_data, to_write);
|
||||||
res += 64;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
res -= 64;
|
|
||||||
return false;
|
return false;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
|
res += success ? 64 : 0 - 64;
|
||||||
return success;
|
return success;
|
||||||
}())
|
}())
|
||||||
{
|
{
|
||||||
@ -3695,7 +3717,8 @@ void do_cell_atomic_128_store(u32 addr, const void* to_write)
|
|||||||
vm::_ref<atomic_t<u32>>(addr) += 0;
|
vm::_ref<atomic_t<u32>>(addr) += 0;
|
||||||
|
|
||||||
// Hard lock
|
// Hard lock
|
||||||
vm::writer_lock lock(addr);
|
auto spu = cpu ? cpu->try_get<spu_thread>() : nullptr;
|
||||||
|
vm::writer_lock lock(addr, spu ? spu->range_lock : nullptr);
|
||||||
mov_rdata(sdata, *static_cast<const spu_rdata_t*>(to_write));
|
mov_rdata(sdata, *static_cast<const spu_rdata_t*>(to_write));
|
||||||
vm::reservation_acquire(addr) += 32;
|
vm::reservation_acquire(addr) += 32;
|
||||||
}
|
}
|
||||||
@ -4461,7 +4484,7 @@ bool spu_thread::reservation_check(u32 addr, const decltype(rdata)& data) const
|
|||||||
// Set range_lock first optimistically
|
// Set range_lock first optimistically
|
||||||
range_lock->store(u64{128} << 32 | addr);
|
range_lock->store(u64{128} << 32 | addr);
|
||||||
|
|
||||||
u64 lock_val = vm::g_range_lock;
|
u64 lock_val = *std::prev(std::end(vm::g_range_lock_set));
|
||||||
u64 old_lock = 0;
|
u64 old_lock = 0;
|
||||||
|
|
||||||
while (lock_val != old_lock)
|
while (lock_val != old_lock)
|
||||||
@ -4516,7 +4539,7 @@ bool spu_thread::reservation_check(u32 addr, const decltype(rdata)& data) const
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
old_lock = std::exchange(lock_val, vm::g_range_lock);
|
old_lock = std::exchange(lock_val, *std::prev(std::end(vm::g_range_lock_set)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!range_lock->load()) [[unlikely]]
|
if (!range_lock->load()) [[unlikely]]
|
||||||
|
@ -79,15 +79,7 @@ constexpr u32 c_max_ppu_name_size = 28;
|
|||||||
void _sys_ppu_thread_exit(ppu_thread& ppu, u64 errorcode)
|
void _sys_ppu_thread_exit(ppu_thread& ppu, u64 errorcode)
|
||||||
{
|
{
|
||||||
ppu.state += cpu_flag::wait;
|
ppu.state += cpu_flag::wait;
|
||||||
|
u64 writer_mask = 0;
|
||||||
// Need to wait until the current writer finish
|
|
||||||
if (ppu.state & cpu_flag::memory)
|
|
||||||
{
|
|
||||||
while (vm::g_range_lock)
|
|
||||||
{
|
|
||||||
busy_wait(200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sys_ppu_thread.trace("_sys_ppu_thread_exit(errorcode=0x%llx)", errorcode);
|
sys_ppu_thread.trace("_sys_ppu_thread_exit(errorcode=0x%llx)", errorcode);
|
||||||
|
|
||||||
@ -126,6 +118,9 @@ void _sys_ppu_thread_exit(ppu_thread& ppu, u64 errorcode)
|
|||||||
old_ppu = g_fxo->get<ppu_thread_cleaner>().clean(std::move(idm::find_unlocked<named_thread<ppu_thread>>(ppu.id)->second));
|
old_ppu = g_fxo->get<ppu_thread_cleaner>().clean(std::move(idm::find_unlocked<named_thread<ppu_thread>>(ppu.id)->second));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get writers mask (wait for all current writers to quit)
|
||||||
|
writer_mask = vm::g_range_lock_bits[1];
|
||||||
|
|
||||||
// Unqueue
|
// Unqueue
|
||||||
lv2_obj::sleep(ppu);
|
lv2_obj::sleep(ppu);
|
||||||
notify.cleanup();
|
notify.cleanup();
|
||||||
@ -154,6 +149,15 @@ void _sys_ppu_thread_exit(ppu_thread& ppu, u64 errorcode)
|
|||||||
// It is detached from IDM now so join must be done explicitly now
|
// It is detached from IDM now so join must be done explicitly now
|
||||||
*static_cast<named_thread<ppu_thread>*>(old_ppu.get()) = thread_state::finished;
|
*static_cast<named_thread<ppu_thread>*>(old_ppu.get()) = thread_state::finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Need to wait until the current writers finish
|
||||||
|
if (ppu.state & cpu_flag::memory)
|
||||||
|
{
|
||||||
|
for (; writer_mask; writer_mask &= vm::g_range_lock_bits[1])
|
||||||
|
{
|
||||||
|
busy_wait(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 sys_ppu_thread_yield(ppu_thread& ppu)
|
s32 sys_ppu_thread_yield(ppu_thread& ppu)
|
||||||
|
@ -70,14 +70,16 @@ namespace vm
|
|||||||
// Memory mutex acknowledgement
|
// Memory mutex acknowledgement
|
||||||
thread_local atomic_t<cpu_thread*>* g_tls_locked = nullptr;
|
thread_local atomic_t<cpu_thread*>* g_tls_locked = nullptr;
|
||||||
|
|
||||||
// "Unique locked" range lock, as opposed to "shared" range locks from set
|
|
||||||
atomic_t<u64> g_range_lock = 0;
|
|
||||||
|
|
||||||
// Memory mutex: passive locks
|
// Memory mutex: passive locks
|
||||||
std::array<atomic_t<cpu_thread*>, g_cfg.core.ppu_threads.max> g_locks{};
|
std::array<atomic_t<cpu_thread*>, g_cfg.core.ppu_threads.max> g_locks{};
|
||||||
|
|
||||||
// Range lock slot allocation bits
|
// Range lock slot allocation bits
|
||||||
atomic_t<u64> g_range_lock_bits{};
|
atomic_t<u64, 64> g_range_lock_bits[2]{};
|
||||||
|
|
||||||
|
auto& get_range_lock_bits(bool is_exclusive_range)
|
||||||
|
{
|
||||||
|
return g_range_lock_bits[+is_exclusive_range];
|
||||||
|
}
|
||||||
|
|
||||||
// Memory range lock slots (sparse atomics)
|
// Memory range lock slots (sparse atomics)
|
||||||
atomic_t<u64, 64> g_range_lock_set[64]{};
|
atomic_t<u64, 64> g_range_lock_set[64]{};
|
||||||
@ -138,9 +140,10 @@ namespace vm
|
|||||||
|
|
||||||
atomic_t<u64, 64>* alloc_range_lock()
|
atomic_t<u64, 64>* alloc_range_lock()
|
||||||
{
|
{
|
||||||
const auto [bits, ok] = g_range_lock_bits.fetch_op([](u64& bits)
|
const auto [bits, ok] = get_range_lock_bits(false).fetch_op([](u64& bits)
|
||||||
{
|
{
|
||||||
if (~bits) [[likely]]
|
// MSB is reserved for locking with memory setting changes
|
||||||
|
if ((~(bits | (bits + 1))) << 1) [[likely]]
|
||||||
{
|
{
|
||||||
bits |= bits + 1;
|
bits |= bits + 1;
|
||||||
return true;
|
return true;
|
||||||
@ -157,6 +160,9 @@ namespace vm
|
|||||||
return &g_range_lock_set[std::countr_one(bits)];
|
return &g_range_lock_set[std::countr_one(bits)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
static u64 for_all_range_locks(u64 input, F func);
|
||||||
|
|
||||||
void range_lock_internal(atomic_t<u64, 64>* range_lock, u32 begin, u32 size)
|
void range_lock_internal(atomic_t<u64, 64>* range_lock, u32 begin, u32 size)
|
||||||
{
|
{
|
||||||
perf_meter<"RHW_LOCK"_u64> perf0(0);
|
perf_meter<"RHW_LOCK"_u64> perf0(0);
|
||||||
@ -168,32 +174,44 @@ namespace vm
|
|||||||
range_lock->store(to_store);
|
range_lock->store(to_store);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u64 i = 0;; i++)
|
for (u64 i = 0, to_clear = umax;; i++)
|
||||||
{
|
{
|
||||||
const u64 lock_val = g_range_lock.load();
|
|
||||||
const u64 is_share = g_shmem[begin >> 16].load();
|
const u64 is_share = g_shmem[begin >> 16].load();
|
||||||
|
to_clear &= get_range_lock_bits(true);
|
||||||
|
|
||||||
u64 lock_addr = static_cast<u32>(lock_val); // -> u64
|
const u64 busy = for_all_range_locks(to_clear, [&](u64 addr_exec, u32 size_exec)
|
||||||
u32 lock_size = static_cast<u32>(lock_val << range_bits >> (range_bits + 32));
|
|
||||||
|
|
||||||
u64 addr = begin;
|
|
||||||
|
|
||||||
if ((lock_val & range_full_mask) == range_locked) [[likely]]
|
|
||||||
{
|
{
|
||||||
lock_size = 128;
|
u64 addr = begin;
|
||||||
|
|
||||||
if (is_share)
|
if ((size_exec & (range_full_mask >> 32)) == (range_locked >> 32)) [[likely]]
|
||||||
{
|
{
|
||||||
addr = static_cast<u16>(addr) | is_share;
|
size_exec = 128;
|
||||||
lock_addr = lock_val;
|
|
||||||
|
if (is_share)
|
||||||
|
{
|
||||||
|
addr = static_cast<u16>(addr) | is_share;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (addr + size <= lock_addr || addr >= lock_addr + lock_size) [[likely]]
|
size_exec = (size_exec << range_bits) >> range_bits;
|
||||||
|
|
||||||
|
// TODO (currently not possible): handle 2 64K pages (inverse range), or more pages
|
||||||
|
if (u64 is_shared = g_shmem[addr_exec >> 16]) [[unlikely]]
|
||||||
|
{
|
||||||
|
addr_exec = static_cast<u16>(addr_exec) | is_shared;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addr <= addr_exec + size_exec - 1 && addr_exec <= addr + size - 1) [[unlikely]]
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!busy) [[likely]]
|
||||||
{
|
{
|
||||||
const u64 new_lock_val = g_range_lock.load();
|
if (vm::check_addr(begin, vm::page_readable, size)) [[likely]]
|
||||||
|
|
||||||
if (vm::check_addr(begin, vm::page_readable, size) && (!new_lock_val || new_lock_val == lock_val)) [[likely]]
|
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -265,7 +283,7 @@ namespace vm
|
|||||||
|
|
||||||
// Use ptr difference to determine location
|
// Use ptr difference to determine location
|
||||||
const auto diff = range_lock - g_range_lock_set;
|
const auto diff = range_lock - g_range_lock_set;
|
||||||
g_range_lock_bits &= ~(1ull << diff);
|
g_range_lock_bits[0] &= ~(1ull << diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename F>
|
template <typename F>
|
||||||
@ -295,13 +313,13 @@ namespace vm
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _lock_main_range_lock(u64 flags, u32 addr, u32 size)
|
static atomic_t<u64, 64>* _lock_main_range_lock(u64 flags, u32 addr, u32 size)
|
||||||
{
|
{
|
||||||
// Shouldn't really happen
|
// Shouldn't really happen
|
||||||
if (size == 0)
|
if (size == 0)
|
||||||
{
|
{
|
||||||
vm_log.warning("Tried to lock empty range (flags=0x%x, addr=0x%x)", flags >> 32, addr);
|
vm_log.warning("Tried to lock empty range (flags=0x%x, addr=0x%x)", flags >> 32, addr);
|
||||||
return;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit to <512 MiB at once; make sure if it operates on big amount of data, it's page-aligned
|
// Limit to <512 MiB at once; make sure if it operates on big amount of data, it's page-aligned
|
||||||
@ -311,7 +329,8 @@ namespace vm
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Block or signal new range locks
|
// Block or signal new range locks
|
||||||
g_range_lock = addr | u64{size} << 32 | flags;
|
auto range_lock = &*std::prev(std::end(vm::g_range_lock_set));
|
||||||
|
*range_lock = addr | u64{size} << 32 | flags;
|
||||||
|
|
||||||
utils::prefetch_read(g_range_lock_set + 0);
|
utils::prefetch_read(g_range_lock_set + 0);
|
||||||
utils::prefetch_read(g_range_lock_set + 2);
|
utils::prefetch_read(g_range_lock_set + 2);
|
||||||
@ -319,7 +338,7 @@ namespace vm
|
|||||||
|
|
||||||
const auto range = utils::address_range::start_length(addr, size);
|
const auto range = utils::address_range::start_length(addr, size);
|
||||||
|
|
||||||
u64 to_clear = g_range_lock_bits.load();
|
u64 to_clear = get_range_lock_bits(false).load();
|
||||||
|
|
||||||
while (to_clear)
|
while (to_clear)
|
||||||
{
|
{
|
||||||
@ -340,22 +359,21 @@ namespace vm
|
|||||||
|
|
||||||
utils::pause();
|
utils::pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return range_lock;
|
||||||
}
|
}
|
||||||
|
|
||||||
void passive_lock(cpu_thread& cpu)
|
void passive_lock(cpu_thread& cpu)
|
||||||
{
|
{
|
||||||
|
ensure(cpu.state & cpu_flag::wait);
|
||||||
|
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
|
|
||||||
if (!g_tls_locked || *g_tls_locked != &cpu) [[unlikely]]
|
if (!g_tls_locked || *g_tls_locked != &cpu) [[unlikely]]
|
||||||
{
|
{
|
||||||
_register_lock(&cpu);
|
_register_lock(&cpu);
|
||||||
|
|
||||||
if (cpu.state & cpu_flag::memory) [[likely]]
|
if (!get_range_lock_bits(true))
|
||||||
{
|
|
||||||
cpu.state -= cpu_flag::memory;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!g_range_lock)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -367,19 +385,18 @@ namespace vm
|
|||||||
{
|
{
|
||||||
for (u64 i = 0;; i++)
|
for (u64 i = 0;; i++)
|
||||||
{
|
{
|
||||||
|
if (cpu.is_paused())
|
||||||
|
{
|
||||||
|
// Assume called from cpu_thread::check_state(), it can handle the pause flags better
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (i < 100)
|
if (i < 100)
|
||||||
busy_wait(200);
|
busy_wait(200);
|
||||||
else
|
else
|
||||||
std::this_thread::yield();
|
std::this_thread::yield();
|
||||||
|
|
||||||
if (g_range_lock)
|
if (!get_range_lock_bits(true)) [[likely]]
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
cpu.state -= cpu_flag::memory;
|
|
||||||
|
|
||||||
if (!g_range_lock) [[likely]]
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -427,18 +444,22 @@ namespace vm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writer_lock::writer_lock()
|
writer_lock::writer_lock() noexcept
|
||||||
: writer_lock(0, 1)
|
: writer_lock(0, nullptr, 1)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
writer_lock::writer_lock(u32 const addr, u32 const size, u64 const flags)
|
writer_lock::writer_lock(u32 const addr, atomic_t<u64, 64>* range_lock, u32 const size, u64 const flags) noexcept
|
||||||
|
: range_lock(range_lock)
|
||||||
{
|
{
|
||||||
auto cpu = get_current_cpu_thread();
|
cpu_thread* cpu{};
|
||||||
|
|
||||||
if (cpu)
|
if (g_tls_locked)
|
||||||
{
|
{
|
||||||
if (!g_tls_locked || *g_tls_locked != cpu || cpu->state & cpu_flag::wait)
|
cpu = get_current_cpu_thread();
|
||||||
|
AUDIT(cpu);
|
||||||
|
|
||||||
|
if (*g_tls_locked != cpu || cpu->state & cpu_flag::wait)
|
||||||
{
|
{
|
||||||
cpu = nullptr;
|
cpu = nullptr;
|
||||||
}
|
}
|
||||||
@ -448,18 +469,51 @@ namespace vm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool to_prepare_memory = addr >= 0x10000;
|
||||||
|
|
||||||
for (u64 i = 0;; i++)
|
for (u64 i = 0;; i++)
|
||||||
{
|
{
|
||||||
if (g_range_lock || !g_range_lock.compare_and_swap_test(0, addr | u64{size} << 32 | flags))
|
auto& bits = get_range_lock_bits(true);
|
||||||
|
|
||||||
|
if (!range_lock || addr < 0x10000)
|
||||||
{
|
{
|
||||||
if (i < 100)
|
if (!bits && bits.compare_and_swap_test(0, u64{umax}))
|
||||||
busy_wait(200);
|
{
|
||||||
else
|
break;
|
||||||
std::this_thread::yield();
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
break;
|
range_lock->release(addr | u64{size} << 32 | flags);
|
||||||
|
|
||||||
|
const auto diff = range_lock - g_range_lock_set;
|
||||||
|
|
||||||
|
if (bits != umax && !bits.bit_test_set(diff))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
range_lock->release(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < 100)
|
||||||
|
{
|
||||||
|
if (to_prepare_memory)
|
||||||
|
{
|
||||||
|
// We have some spare time, prepare cache lines (todo: reservation tests here)
|
||||||
|
utils::prefetch_write(vm::get_super_ptr(addr));
|
||||||
|
utils::prefetch_write(vm::get_super_ptr(addr) + 64);
|
||||||
|
to_prepare_memory = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
busy_wait(200);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::this_thread::yield();
|
||||||
|
|
||||||
|
// Thread may have been switched or the cache clue has been undermined, cache needs to be prapred again
|
||||||
|
to_prepare_memory = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -469,7 +523,7 @@ namespace vm
|
|||||||
|
|
||||||
for (auto lock = g_locks.cbegin(), end = lock + g_cfg.core.ppu_threads; lock != end; lock++)
|
for (auto lock = g_locks.cbegin(), end = lock + g_cfg.core.ppu_threads; lock != end; lock++)
|
||||||
{
|
{
|
||||||
if (auto ptr = +*lock; ptr && !(ptr->state & cpu_flag::memory))
|
if (auto ptr = +*lock; ptr && ptr->state.none_of(cpu_flag::wait + cpu_flag::memory))
|
||||||
{
|
{
|
||||||
ptr->state.test_and_set(cpu_flag::memory);
|
ptr->state.test_and_set(cpu_flag::memory);
|
||||||
}
|
}
|
||||||
@ -487,13 +541,13 @@ namespace vm
|
|||||||
utils::prefetch_read(g_range_lock_set + 2);
|
utils::prefetch_read(g_range_lock_set + 2);
|
||||||
utils::prefetch_read(g_range_lock_set + 4);
|
utils::prefetch_read(g_range_lock_set + 4);
|
||||||
|
|
||||||
u64 to_clear = g_range_lock_bits.load();
|
u64 to_clear = get_range_lock_bits(false);
|
||||||
|
|
||||||
u64 point = addr1 / 128;
|
u64 point = addr1 / 128;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
to_clear = for_all_range_locks(to_clear, [&](u64 addr2, u32 size2)
|
to_clear = for_all_range_locks(to_clear & ~get_range_lock_bits(true), [&](u64 addr2, u32 size2)
|
||||||
{
|
{
|
||||||
// Split and check every 64K page separately
|
// Split and check every 64K page separately
|
||||||
for (u64 hi = addr2 >> 16, max = (addr2 + size2 - 1) >> 16; hi <= max; hi++)
|
for (u64 hi = addr2 >> 16, max = (addr2 + size2 - 1) >> 16; hi <= max; hi++)
|
||||||
@ -523,6 +577,13 @@ namespace vm
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (to_prepare_memory)
|
||||||
|
{
|
||||||
|
utils::prefetch_write(vm::get_super_ptr(addr));
|
||||||
|
utils::prefetch_write(vm::get_super_ptr(addr) + 64);
|
||||||
|
to_prepare_memory = false;
|
||||||
|
}
|
||||||
|
|
||||||
utils::pause();
|
utils::pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,6 +593,13 @@ namespace vm
|
|||||||
{
|
{
|
||||||
while (!(ptr->state & cpu_flag::wait))
|
while (!(ptr->state & cpu_flag::wait))
|
||||||
{
|
{
|
||||||
|
if (to_prepare_memory)
|
||||||
|
{
|
||||||
|
utils::prefetch_write(vm::get_super_ptr(addr));
|
||||||
|
utils::prefetch_write(vm::get_super_ptr(addr) + 64);
|
||||||
|
to_prepare_memory = false;
|
||||||
|
}
|
||||||
|
|
||||||
utils::pause();
|
utils::pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -544,11 +612,6 @@ namespace vm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writer_lock::~writer_lock()
|
|
||||||
{
|
|
||||||
g_range_lock = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 reservation_lock_internal(u32 addr, atomic_t<u64>& res)
|
u64 reservation_lock_internal(u32 addr, atomic_t<u64>& res)
|
||||||
{
|
{
|
||||||
for (u64 i = 0;; i++)
|
for (u64 i = 0;; i++)
|
||||||
@ -672,7 +735,7 @@ namespace vm
|
|||||||
const bool is_noop = bflags & page_size_4k && utils::c_page_size > 4096;
|
const bool is_noop = bflags & page_size_4k && utils::c_page_size > 4096;
|
||||||
|
|
||||||
// Lock range being mapped
|
// Lock range being mapped
|
||||||
_lock_main_range_lock(range_allocation, addr, size);
|
auto range_lock = _lock_main_range_lock(range_allocation, addr, size);
|
||||||
|
|
||||||
if (shm && shm->flags() != 0 && shm->info++)
|
if (shm && shm->flags() != 0 && shm->info++)
|
||||||
{
|
{
|
||||||
@ -788,6 +851,8 @@ namespace vm
|
|||||||
fmt::throw_exception("Concurrent access (addr=0x%x, size=0x%x, flags=0x%x, current_addr=0x%x)", addr, size, flags, i * 4096);
|
fmt::throw_exception("Concurrent access (addr=0x%x, size=0x%x, flags=0x%x, current_addr=0x%x)", addr, size, flags, i * 4096);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
range_lock->release(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool page_protect(u32 addr, u32 size, u8 flags_test, u8 flags_set, u8 flags_clear)
|
bool page_protect(u32 addr, u32 size, u8 flags_test, u8 flags_set, u8 flags_clear)
|
||||||
@ -845,7 +910,7 @@ namespace vm
|
|||||||
safe_bits |= range_writable;
|
safe_bits |= range_writable;
|
||||||
|
|
||||||
// Protect range locks from observing changes in memory protection
|
// Protect range locks from observing changes in memory protection
|
||||||
_lock_main_range_lock(safe_bits, start * 4096, page_size);
|
auto range_lock = _lock_main_range_lock(safe_bits, start * 4096, page_size);
|
||||||
|
|
||||||
for (u32 j = start; j < i; j++)
|
for (u32 j = start; j < i; j++)
|
||||||
{
|
{
|
||||||
@ -857,6 +922,8 @@ namespace vm
|
|||||||
const auto protection = start_value & page_writable ? utils::protection::rw : (start_value & page_readable ? utils::protection::ro : utils::protection::no);
|
const auto protection = start_value & page_writable ? utils::protection::rw : (start_value & page_readable ? utils::protection::ro : utils::protection::no);
|
||||||
utils::memory_protect(g_base_addr + start * 4096, page_size, protection);
|
utils::memory_protect(g_base_addr + start * 4096, page_size, protection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
range_lock->release(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
start_value = new_val;
|
start_value = new_val;
|
||||||
@ -904,7 +971,7 @@ namespace vm
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Protect range locks from actual memory protection changes
|
// Protect range locks from actual memory protection changes
|
||||||
_lock_main_range_lock(range_allocation, addr, size);
|
auto range_lock = _lock_main_range_lock(range_allocation, addr, size);
|
||||||
|
|
||||||
if (shm && shm->flags() != 0 && g_shmem[addr >> 16])
|
if (shm && shm->flags() != 0 && g_shmem[addr >> 16])
|
||||||
{
|
{
|
||||||
@ -965,6 +1032,7 @@ namespace vm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
range_lock->release(0);
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1966,11 +2034,13 @@ namespace vm
|
|||||||
{
|
{
|
||||||
auto* range_lock = alloc_range_lock(); // Released at the end of function
|
auto* range_lock = alloc_range_lock(); // Released at the end of function
|
||||||
|
|
||||||
range_lock->store(begin | (u64{size} << 32));
|
auto mem_lock = &*std::prev(std::end(vm::g_range_lock_set));
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
const u64 lock_val = g_range_lock.load();
|
range_lock->store(begin | (u64{size} << 32));
|
||||||
|
|
||||||
|
const u64 lock_val = mem_lock->load();
|
||||||
const u64 is_share = g_shmem[begin >> 16].load();
|
const u64 is_share = g_shmem[begin >> 16].load();
|
||||||
|
|
||||||
u64 lock_addr = static_cast<u32>(lock_val); // -> u64
|
u64 lock_addr = static_cast<u32>(lock_val); // -> u64
|
||||||
@ -1993,7 +2063,7 @@ namespace vm
|
|||||||
{
|
{
|
||||||
if (vm::check_addr(begin, is_write ? page_writable : page_readable, size)) [[likely]]
|
if (vm::check_addr(begin, is_write ? page_writable : page_readable, size)) [[likely]]
|
||||||
{
|
{
|
||||||
const u64 new_lock_val = g_range_lock.load();
|
const u64 new_lock_val = mem_lock->load();
|
||||||
|
|
||||||
if (!new_lock_val || new_lock_val == lock_val) [[likely]]
|
if (!new_lock_val || new_lock_val == lock_val) [[likely]]
|
||||||
{
|
{
|
||||||
@ -2026,8 +2096,6 @@ namespace vm
|
|||||||
range_lock->release(0);
|
range_lock->release(0);
|
||||||
|
|
||||||
busy_wait(200);
|
busy_wait(200);
|
||||||
|
|
||||||
range_lock->store(begin | (u64{size} << 32));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool result = try_access_internal(begin, ptr, size, is_write);
|
const bool result = try_access_internal(begin, ptr, size, is_write);
|
||||||
@ -2071,7 +2139,7 @@ namespace vm
|
|||||||
std::memset(g_reservations, 0, sizeof(g_reservations));
|
std::memset(g_reservations, 0, sizeof(g_reservations));
|
||||||
std::memset(g_shmem, 0, sizeof(g_shmem));
|
std::memset(g_shmem, 0, sizeof(g_shmem));
|
||||||
std::memset(g_range_lock_set, 0, sizeof(g_range_lock_set));
|
std::memset(g_range_lock_set, 0, sizeof(g_range_lock_set));
|
||||||
g_range_lock_bits = 0;
|
std::memset(g_range_lock_bits, 0, sizeof(g_range_lock_bits));
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
utils::memory_release(g_hook_addr, 0x800000000);
|
utils::memory_release(g_hook_addr, 0x800000000);
|
||||||
@ -2104,7 +2172,7 @@ namespace vm
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::memset(g_range_lock_set, 0, sizeof(g_range_lock_set));
|
std::memset(g_range_lock_set, 0, sizeof(g_range_lock_set));
|
||||||
g_range_lock_bits = 0;
|
std::memset(g_range_lock_bits, 0, sizeof(g_range_lock_bits));
|
||||||
}
|
}
|
||||||
|
|
||||||
void save(utils::serial& ar)
|
void save(utils::serial& ar)
|
||||||
@ -2209,8 +2277,6 @@ namespace vm
|
|||||||
loc = std::make_shared<block_t>(ar, shared);
|
loc = std::make_shared<block_t>(ar, shared);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g_range_lock = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 get_shm_addr(const std::shared_ptr<utils::shm>& shared)
|
u32 get_shm_addr(const std::shared_ptr<utils::shm>& shared)
|
||||||
|
@ -27,7 +27,7 @@ namespace vm
|
|||||||
range_bits = 3,
|
range_bits = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
extern atomic_t<u64> g_range_lock;
|
extern atomic_t<u64, 64> g_range_lock_bits[2];
|
||||||
|
|
||||||
extern atomic_t<u64> g_shmem[];
|
extern atomic_t<u64> g_shmem[];
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ namespace vm
|
|||||||
__asm__(""); // Tiny barrier
|
__asm__(""); // Tiny barrier
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!g_range_lock)
|
if (!g_range_lock_bits[1]) [[likely]]
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -82,10 +82,12 @@ namespace vm
|
|||||||
|
|
||||||
struct writer_lock final
|
struct writer_lock final
|
||||||
{
|
{
|
||||||
|
atomic_t<u64, 64>* range_lock;
|
||||||
|
|
||||||
writer_lock(const writer_lock&) = delete;
|
writer_lock(const writer_lock&) = delete;
|
||||||
writer_lock& operator=(const writer_lock&) = delete;
|
writer_lock& operator=(const writer_lock&) = delete;
|
||||||
writer_lock();
|
writer_lock() noexcept;
|
||||||
writer_lock(u32 addr, u32 size = 0, u64 flags = range_locked);
|
writer_lock(u32 addr, atomic_t<u64, 64>* range_lock = nullptr, u32 size = 128, u64 flags = range_locked) noexcept;
|
||||||
~writer_lock();
|
~writer_lock() noexcept;
|
||||||
};
|
};
|
||||||
} // namespace vm
|
} // namespace vm
|
||||||
|
Loading…
Reference in New Issue
Block a user