2018-09-25 22:34:45 +02:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include "vm.h"
|
2020-10-07 00:14:35 +02:00
|
|
|
#include "vm_locking.h"
|
2018-09-25 22:34:45 +02:00
|
|
|
#include "Utilities/cond.h"
|
2019-07-27 00:34:10 +02:00
|
|
|
#include "util/atomic.hpp"
|
2020-10-07 00:14:35 +02:00
|
|
|
#include <functional>
|
|
|
|
|
|
|
|
|
|
extern bool g_use_rtm;
|
2018-09-29 00:12:00 +02:00
|
|
|
|
2018-09-25 22:34:45 +02:00
|
|
|
namespace vm
|
|
|
|
|
{
|
2020-10-08 15:13:55 +02:00
|
|
|
enum : u64
|
2020-09-02 23:58:29 +02:00
|
|
|
{
|
2020-10-08 15:13:55 +02:00
|
|
|
rsrv_lock_mask = 127,
|
|
|
|
|
rsrv_unique_lock = 64,
|
|
|
|
|
rsrv_shared_mask = 63,
|
2020-09-02 23:58:29 +02:00
|
|
|
};
|
|
|
|
|
|
2018-09-25 22:34:45 +02:00
|
|
|
// Get reservation status for further atomic update: last update timestamp
|
|
|
|
|
inline atomic_t<u64>& reservation_acquire(u32 addr, u32 size)
|
|
|
|
|
{
|
|
|
|
|
// Access reservation info: stamp and the lock bit
|
2020-04-11 10:16:28 +02:00
|
|
|
return *reinterpret_cast<atomic_t<u64>*>(g_reservations + (addr & 0xff80) / 2);
|
2018-09-25 22:34:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update reservation status
|
2020-08-15 06:56:34 +02:00
|
|
|
inline std::pair<bool, u64> try_reservation_update(u32 addr, u32 size, bool lsb = false)
|
2018-09-25 22:34:45 +02:00
|
|
|
{
|
|
|
|
|
// Update reservation info with new timestamp
|
2020-08-15 06:56:34 +02:00
|
|
|
auto& res = reservation_acquire(addr, size);
|
|
|
|
|
const u64 rtime = res;
|
|
|
|
|
|
|
|
|
|
return {!(rtime & 127) && res.compare_and_swap_test(rtime, rtime + 128), rtime};
|
2018-09-25 22:34:45 +02:00
|
|
|
}
|
|
|
|
|
|
2020-08-15 06:56:34 +02:00
|
|
|
void reservation_update(u32 addr, u32 size, bool lsb = false);
|
|
|
|
|
|
2018-09-25 22:34:45 +02:00
|
|
|
// Get reservation sync variable
|
2019-09-19 01:57:08 +02:00
|
|
|
inline atomic_t<u64>& reservation_notifier(u32 addr, u32 size)
|
2018-09-25 22:34:45 +02:00
|
|
|
{
|
2020-04-11 10:16:28 +02:00
|
|
|
return *reinterpret_cast<atomic_t<u64>*>(g_reservations + (addr & 0xff80) / 2);
|
2018-09-25 22:34:45 +02:00
|
|
|
}
|
|
|
|
|
|
2020-10-08 15:13:55 +02:00
|
|
|
u64 reservation_lock_internal(u32, atomic_t<u64>&);
|
2020-09-02 23:58:29 +02:00
|
|
|
|
2020-10-08 15:13:55 +02:00
|
|
|
void reservation_shared_lock_internal(atomic_t<u64>&);
|
|
|
|
|
|
|
|
|
|
inline bool reservation_try_lock(atomic_t<u64>& res, u64 rtime)
|
2020-09-02 23:58:29 +02:00
|
|
|
{
|
2020-10-08 15:13:55 +02:00
|
|
|
if (res.compare_and_swap_test(rtime, rtime | rsrv_unique_lock)) [[likely]]
|
2020-09-02 23:58:29 +02:00
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-09-25 22:34:45 +02:00
|
|
|
|
2020-10-08 15:13:55 +02:00
|
|
|
inline std::pair<atomic_t<u64>&, u64> reservation_lock(u32 addr)
|
2018-09-25 22:34:45 +02:00
|
|
|
{
|
2020-10-08 15:13:55 +02:00
|
|
|
auto res = &vm::reservation_acquire(addr, 1);
|
2020-09-02 23:58:29 +02:00
|
|
|
auto rtime = res->load();
|
2018-09-25 22:34:45 +02:00
|
|
|
|
2020-10-08 15:13:55 +02:00
|
|
|
if (rtime & 127 || !reservation_try_lock(*res, rtime)) [[unlikely]]
|
2018-09-25 22:34:45 +02:00
|
|
|
{
|
2020-05-23 15:36:32 +02:00
|
|
|
static atomic_t<u64> no_lock{};
|
|
|
|
|
|
2020-10-08 15:13:55 +02:00
|
|
|
rtime = reservation_lock_internal(addr, *res);
|
2020-09-02 23:58:29 +02:00
|
|
|
|
|
|
|
|
if (rtime == umax)
|
2020-05-23 15:36:32 +02:00
|
|
|
{
|
|
|
|
|
res = &no_lock;
|
|
|
|
|
}
|
2018-09-25 22:34:45 +02:00
|
|
|
}
|
|
|
|
|
|
2020-09-02 23:58:29 +02:00
|
|
|
return {*res, rtime};
|
2020-05-08 19:41:15 +02:00
|
|
|
}
|
2020-10-07 00:14:35 +02:00
|
|
|
|
|
|
|
|
void reservation_op_internal(u32 addr, std::function<bool()> func);
|
|
|
|
|
|
|
|
|
|
template <typename T, typename AT = u32, typename F>
|
|
|
|
|
SAFE_BUFFERS inline auto reservation_op(_ptr_base<T, AT> ptr, F op)
|
|
|
|
|
{
|
|
|
|
|
// Atomic operation will be performed on aligned 128 bytes of data, so the data size and alignment must comply
|
|
|
|
|
static_assert(sizeof(T) <= 128 && alignof(T) == sizeof(T), "vm::reservation_op: unsupported type");
|
|
|
|
|
static_assert(std::is_trivially_copyable_v<T>, "vm::reservation_op: not triv copyable (optimization)");
|
|
|
|
|
|
|
|
|
|
// Use "super" pointer to prevent access violation handling during atomic op
|
|
|
|
|
const auto sptr = vm::get_super_ptr<T>(static_cast<u32>(ptr.addr()));
|
|
|
|
|
|
|
|
|
|
// Use 128-byte aligned addr
|
|
|
|
|
const u32 addr = static_cast<u32>(ptr.addr()) & -128;
|
|
|
|
|
|
|
|
|
|
if (g_use_rtm)
|
|
|
|
|
{
|
|
|
|
|
auto& res = vm::reservation_acquire(addr, 128);
|
|
|
|
|
|
|
|
|
|
// Stage 1: single optimistic transaction attempt
|
|
|
|
|
unsigned status = _XBEGIN_STARTED;
|
2020-10-08 15:13:55 +02:00
|
|
|
u64 _old = 0;
|
2020-10-07 00:14:35 +02:00
|
|
|
|
|
|
|
|
#ifndef _MSC_VER
|
|
|
|
|
__asm__ goto ("xbegin %l[stage2];" ::: "memory" : stage2);
|
|
|
|
|
#else
|
|
|
|
|
status = _xbegin();
|
|
|
|
|
if (status == _XBEGIN_STARTED)
|
|
|
|
|
#endif
|
|
|
|
|
{
|
2020-10-08 15:13:55 +02:00
|
|
|
if (res & rsrv_unique_lock)
|
|
|
|
|
{
|
|
|
|
|
#ifndef _MSC_VER
|
|
|
|
|
__asm__ volatile ("xabort $0;" ::: "memory");
|
|
|
|
|
#else
|
|
|
|
|
_xabort(0);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-07 00:14:35 +02:00
|
|
|
if constexpr (std::is_void_v<std::invoke_result_t<F, T&>>)
|
|
|
|
|
{
|
|
|
|
|
res += 128;
|
|
|
|
|
std::invoke(op, *sptr);
|
|
|
|
|
#ifndef _MSC_VER
|
|
|
|
|
__asm__ volatile ("xend;" ::: "memory");
|
|
|
|
|
#else
|
|
|
|
|
_xend();
|
|
|
|
|
#endif
|
|
|
|
|
res.notify_all();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (auto result = std::invoke(op, *sptr))
|
|
|
|
|
{
|
|
|
|
|
res += 128;
|
|
|
|
|
#ifndef _MSC_VER
|
|
|
|
|
__asm__ volatile ("xend;" ::: "memory");
|
|
|
|
|
#else
|
|
|
|
|
_xend();
|
|
|
|
|
#endif
|
|
|
|
|
res.notify_all();
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
#ifndef _MSC_VER
|
|
|
|
|
__asm__ volatile ("xabort $1;" ::: "memory");
|
|
|
|
|
#else
|
|
|
|
|
_xabort(1);
|
|
|
|
|
#endif
|
|
|
|
|
// Unreachable code
|
|
|
|
|
return std::invoke_result_t<F, T&>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stage2:
|
|
|
|
|
#ifndef _MSC_VER
|
|
|
|
|
__asm__ volatile ("movl %%eax, %0;" : "=r" (status) :: "memory");
|
|
|
|
|
#endif
|
|
|
|
|
if constexpr (!std::is_void_v<std::invoke_result_t<F, T&>>)
|
|
|
|
|
{
|
|
|
|
|
if (_XABORT_CODE(status))
|
|
|
|
|
{
|
|
|
|
|
// Unfortunately, actual function result is not recoverable in this case
|
|
|
|
|
return std::invoke_result_t<F, T&>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Touch memory if transaction failed without RETRY flag on the first attempt (TODO)
|
|
|
|
|
if (!(status & _XABORT_RETRY))
|
|
|
|
|
{
|
|
|
|
|
reinterpret_cast<atomic_t<u8>*>(sptr)->fetch_add(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stage 2: try to lock reservation first
|
2020-10-08 15:13:55 +02:00
|
|
|
_old = res.fetch_add(1);
|
2020-10-07 00:14:35 +02:00
|
|
|
|
|
|
|
|
// Start lightened transaction (TODO: tweaking)
|
2020-10-08 15:13:55 +02:00
|
|
|
while (!(_old & rsrv_unique_lock))
|
2020-10-07 00:14:35 +02:00
|
|
|
{
|
|
|
|
|
#ifndef _MSC_VER
|
|
|
|
|
__asm__ goto ("xbegin %l[retry];" ::: "memory" : retry);
|
|
|
|
|
#else
|
|
|
|
|
status = _xbegin();
|
|
|
|
|
|
|
|
|
|
if (status != _XBEGIN_STARTED) [[unlikely]]
|
|
|
|
|
{
|
|
|
|
|
goto retry;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
if constexpr (std::is_void_v<std::invoke_result_t<F, T&>>)
|
|
|
|
|
{
|
|
|
|
|
std::invoke(op, *sptr);
|
|
|
|
|
#ifndef _MSC_VER
|
|
|
|
|
__asm__ volatile ("xend;" ::: "memory");
|
|
|
|
|
#else
|
|
|
|
|
_xend();
|
|
|
|
|
#endif
|
|
|
|
|
res += 127;
|
|
|
|
|
res.notify_all();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (auto result = std::invoke(op, *sptr))
|
|
|
|
|
{
|
|
|
|
|
#ifndef _MSC_VER
|
|
|
|
|
__asm__ volatile ("xend;" ::: "memory");
|
|
|
|
|
#else
|
|
|
|
|
_xend();
|
|
|
|
|
#endif
|
|
|
|
|
res += 127;
|
|
|
|
|
res.notify_all();
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
#ifndef _MSC_VER
|
|
|
|
|
__asm__ volatile ("xabort $1;" ::: "memory");
|
|
|
|
|
#else
|
|
|
|
|
_xabort(1);
|
|
|
|
|
#endif
|
|
|
|
|
return std::invoke_result_t<F, T&>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
retry:
|
|
|
|
|
#ifndef _MSC_VER
|
|
|
|
|
__asm__ volatile ("movl %%eax, %0;" : "=r" (status) :: "memory");
|
|
|
|
|
#endif
|
|
|
|
|
if (!(status & _XABORT_RETRY)) [[unlikely]]
|
|
|
|
|
{
|
|
|
|
|
if constexpr (!std::is_void_v<std::invoke_result_t<F, T&>>)
|
|
|
|
|
{
|
|
|
|
|
if (_XABORT_CODE(status))
|
|
|
|
|
{
|
|
|
|
|
res -= 1;
|
|
|
|
|
return std::invoke_result_t<F, T&>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stage 3: all failed, heavyweight fallback (see comments at the bottom)
|
|
|
|
|
if constexpr (std::is_void_v<std::invoke_result_t<F, T&>>)
|
|
|
|
|
{
|
|
|
|
|
return vm::reservation_op_internal(addr, [&]
|
|
|
|
|
{
|
|
|
|
|
std::invoke(op, *sptr);
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
auto result = std::invoke_result_t<F, T&>();
|
|
|
|
|
|
|
|
|
|
vm::reservation_op_internal(addr, [&]
|
|
|
|
|
{
|
|
|
|
|
T buf = *sptr;
|
|
|
|
|
|
|
|
|
|
if ((result = std::invoke(op, buf)))
|
|
|
|
|
{
|
|
|
|
|
*sptr = buf;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-08 15:13:55 +02:00
|
|
|
// Perform heavyweight lock
|
|
|
|
|
auto [res, rtime] = vm::reservation_lock(addr);
|
2020-10-07 00:14:35 +02:00
|
|
|
|
|
|
|
|
// Write directly if the op cannot fail
|
|
|
|
|
if constexpr (std::is_void_v<std::invoke_result_t<F, T&>>)
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
vm::writer_lock lock(addr);
|
|
|
|
|
std::invoke(op, *sptr);
|
|
|
|
|
res += 127;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.notify_all();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Make an operational copy of data (TODO: volatile storage?)
|
|
|
|
|
auto result = std::invoke_result_t<F, T&>();
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
vm::writer_lock lock(addr);
|
|
|
|
|
T buf = *sptr;
|
|
|
|
|
|
|
|
|
|
if ((result = std::invoke(op, buf)))
|
|
|
|
|
{
|
|
|
|
|
// If operation succeeds, write the data back
|
|
|
|
|
*sptr = buf;
|
2020-10-08 15:13:55 +02:00
|
|
|
res.release(rtime + 128);
|
2020-10-07 00:14:35 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Operation failed, no memory has been modified
|
2020-10-08 15:13:55 +02:00
|
|
|
res.release(rtime);
|
2020-10-07 00:14:35 +02:00
|
|
|
return std::invoke_result_t<F, T&>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.notify_all();
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For internal usage
|
|
|
|
|
void reservation_escape_internal();
|
|
|
|
|
|
|
|
|
|
// Read memory value in pseudo-atomic manner
|
|
|
|
|
template <typename CPU, typename T, typename AT = u32, typename F>
|
|
|
|
|
SAFE_BUFFERS inline auto reservation_peek(CPU&& cpu, _ptr_base<T, AT> ptr, F op)
|
|
|
|
|
{
|
|
|
|
|
// Atomic operation will be performed on aligned 128 bytes of data, so the data size and alignment must comply
|
|
|
|
|
static_assert(sizeof(T) <= 128 && alignof(T) == sizeof(T), "vm::reservation_peek: unsupported type");
|
|
|
|
|
|
|
|
|
|
// Use "super" pointer to prevent access violation handling during atomic op
|
|
|
|
|
const auto sptr = vm::get_super_ptr<const T>(static_cast<u32>(ptr.addr()));
|
|
|
|
|
|
|
|
|
|
// Use 128-byte aligned addr
|
|
|
|
|
const u32 addr = static_cast<u32>(ptr.addr()) & -128;
|
|
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
if constexpr (std::is_class_v<std::remove_cvref_t<CPU>>)
|
|
|
|
|
{
|
|
|
|
|
if (cpu.test_stopped())
|
|
|
|
|
{
|
|
|
|
|
reservation_escape_internal();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const u64 rtime = vm::reservation_acquire(addr, 128);
|
|
|
|
|
|
|
|
|
|
if (rtime & 127)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Observe data non-atomically and make sure no reservation updates were made
|
|
|
|
|
if constexpr (std::is_void_v<std::invoke_result_t<F, const T&>>)
|
|
|
|
|
{
|
|
|
|
|
std::invoke(op, *sptr);
|
|
|
|
|
|
|
|
|
|
if (rtime == vm::reservation_acquire(addr, 128))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
auto res = std::invoke(op, *sptr);
|
|
|
|
|
|
|
|
|
|
if (rtime == vm::reservation_acquire(addr, 128))
|
|
|
|
|
{
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-08 15:13:55 +02:00
|
|
|
|
|
|
|
|
template <bool Ack = false, typename T, typename F>
|
|
|
|
|
SAFE_BUFFERS inline auto reservation_light_op(T& data, F op)
|
|
|
|
|
{
|
|
|
|
|
// Optimized real ptr -> vm ptr conversion, simply UB if out of range
|
|
|
|
|
const u32 addr = static_cast<u32>(reinterpret_cast<const u8*>(&data) - g_base_addr);
|
|
|
|
|
|
|
|
|
|
// Use "super" pointer to prevent access violation handling during atomic op
|
|
|
|
|
const auto sptr = vm::get_super_ptr<T>(addr);
|
|
|
|
|
|
|
|
|
|
// "Lock" reservation
|
|
|
|
|
auto& res = vm::reservation_acquire(addr, 128);
|
|
|
|
|
|
|
|
|
|
if (res.fetch_add(1) & vm::rsrv_unique_lock) [[unlikely]]
|
|
|
|
|
{
|
|
|
|
|
vm::reservation_shared_lock_internal(res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if constexpr (std::is_void_v<std::invoke_result_t<F, T&>>)
|
|
|
|
|
{
|
|
|
|
|
std::invoke(op, *sptr);
|
|
|
|
|
res += 127;
|
|
|
|
|
|
|
|
|
|
if constexpr (Ack)
|
|
|
|
|
{
|
|
|
|
|
res.notify_all();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
auto result = std::invoke(op, *sptr);
|
|
|
|
|
res += 127;
|
|
|
|
|
|
|
|
|
|
if constexpr (Ack)
|
|
|
|
|
{
|
|
|
|
|
res.notify_all();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-09-25 22:34:45 +02:00
|
|
|
} // namespace vm
|