Compare commits

...

4 Commits

Author SHA1 Message Date
nmzik e679486ed3 net: translate native UDP socket options and microsecond timeouts 2026-10-06 23:41:06 +04:00
Henrique Reis Neves 6ef406542c tests: make kyty_tests build and run every test without LTO (#1110)
avplayer_file_tests compiles libs/avPlayer.cpp into the test, and its
PRINT_NAME() calls Loader::Timer::GetTimeMs(). Add loader/timer.cpp to the
target, like the other tests that compile HLE library sources. CI links the
test with LTO, which drops the unused reference, so the failure only shows
in non-LTO builds such as the README's no-Qt instructions.

Clang with libstdc++ rejects optional<IndirectDescriptor>::emplace() with
no arguments in ShaderRecompilerComputeTests.cpp. Assign the descriptor the
same way the neighbouring test does.

trophy_system is registered with ctest but kyty_tests did not build its
executable, so ctest reported it as Not Run.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:56:48 +04:00
Bartłomiej Wołyńczyk 35c6e8fff6 loader: enable the AMD CPU instruction patcher on macOS (#1014)
* loader: enable AMD CPU instruction patching on macOS

The guest instruction patcher was compiled out on macOS and runtimeLinker forced emulate_amd off,
so under Rosetta every SSE4a EXTRQ/INSERTQ took the SIGILL path (about 18 us each). Build the
patcher on all hosts and let --amd-cpu enable it on macOS like on Windows and Linux. The host
feature probe already reports no SSE4a under Rosetta, so EXTRQ becomes a native trampoline and the
remaining fallbacks keep using the x64 instruction emulator through the host exception handler.

* tests: macos_sse4a covers the guest instruction patcher on macOS

Patch EXTRQ/INSERTQ blobs in an RWX area with a trampoline page behind it, with the host features of
Rosetta (no SSE4a/RDPID/CLWB), and compare against the SIGILL emulator: EXTRQ must become a native
trampoline without SIGILL, INSERTQ stays a trap handled by the emulator, and nothing is touched
without emulate_amd.

* loader: emulate the patcher's VRSQRTPS trap and MWAITX on macOS

The AMD CPU patcher marks VRSQRTPS it cannot relocate by clearing a reserved
VEX.vvvv bit, which raises #UD on every host including Rosetta. The macOS build
left TryEmulateReciprocalSquareRoot and TryEmulateMonitorxMwaitx out, so the
guest died with an unhandled SIGILL at the first such site.

Both are plain SSE2/libc, so enable them on macOS. The Darwin context also
gains ClearUpperYmm so the VEX.128 result zeroes YMM[255:128] when the thread
has the AVX context. macos_sse4a covers the trapped VRSQRTPS and MWAITX.

* tests: macos_sse4a runs a plain VEX.128 VRSQRTPS through the patcher

The existing case started from an instruction that was already marked as a trap. The
new cases feed the ordinary encoding to PatchGuestInstructions and compare the result
with the emulated reference, both through a native trampoline and, when the instruction
cannot be relocated, through the trap mark and the signal emulator.

* loader: compact macOS instruction patcher coverage

Share the emulator dispatch chain while keeping SHA emulation limited to non-Apple hosts and preserving handler order. Remove a duplicate VRSQRTPS signal test already covered by both native and trapped patcher cases, plus blank lines left by removed platform guards.

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-06 21:55:11 +04:00
thenotsoquietninja44-collab 4852721005 net: export connect, getsockopt, sendto, and recvfrom 2026-10-06 21:32:46 +04:00
9 changed files with 524 additions and 64 deletions
+7 -2
View File
@@ -419,7 +419,10 @@ add_executable(trophy_system_tests EXCLUDE_FROM_ALL
target_link_libraries(trophy_system_tests common)
target_include_directories(trophy_system_tests PRIVATE ${inc_headers})
add_executable(avplayer_file_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/AvPlayerFileTests.cpp")
add_executable(avplayer_file_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/AvPlayerFileTests.cpp"
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
)
target_link_libraries(avplayer_file_tests common FFmpeg::ffmpeg ZArchive::zarchive)
target_include_directories(avplayer_file_tests PRIVATE ${inc_headers})
@@ -689,6 +692,7 @@ if(BUILD_TESTING)
memory_tracker_tests
page_manager_tests
archive_file_tests
trophy_system_tests
avplayer_file_tests
bit_array_tests
lru_cache_tests
@@ -719,8 +723,9 @@ if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR
add_executable(macos_sse4a_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/macosSse4aTests.cpp"
"${KYTY_SOURCE_DIR}/loader/x64InstructionEmulator.cpp"
"${KYTY_SOURCE_DIR}/loader/guestInstructionPatcher.cpp"
)
target_link_libraries(macos_sse4a_tests common fmt::fmt Zydis::Zydis)
target_link_libraries(macos_sse4a_tests common fmt::fmt Zydis::Zydis xbyak::xbyak)
target_include_directories(macos_sse4a_tests PRIVATE ${inc_headers})
if(BUILD_TESTING)
add_test(NAME macos_sse4a COMMAND $<TARGET_FILE:macos_sse4a_tests>)
+26 -2
View File
@@ -89,6 +89,10 @@ int KYTY_SYSV_ABI NetBind(int s, const void* addr, uint32_t addrlen) {
return FinishSocketCall(Net::Bind(s, addr, addrlen));
}
int KYTY_SYSV_ABI NetConnect(int s, const void* addr, uint32_t addrlen) {
return FinishSocketCall(Net::Connect(s, addr, addrlen));
}
int KYTY_SYSV_ABI NetListen(int s, int backlog) {
return FinishSocketCall(Net::Listen(s, backlog));
}
@@ -170,14 +174,24 @@ int KYTY_SYSV_ABI NetSocketClose(int s) {
return FinishNetCall(Net::SocketClose(s));
}
int KYTY_SYSV_ABI NetGetsockopt(int s, int level, int optname, void* optval, uint32_t* optlen) {
return FinishSocketCall(Net::Getsockopt(s, level, optname, optval, optlen));
}
int KYTY_SYSV_ABI NetSetsockopt(int s, int level, int optname, const void* optval,
uint32_t optlen) {
return FinishSocketCall(Net::Setsockopt(s, level, optname, optval, optlen));
}
int KYTY_SYSV_ABI NetSend(int s, const void* buf, size_t len, int flags) {
int KYTY_SYSV_ABI NetSendto(int s, const void* buf, size_t len, int flags, const void* addr,
uint32_t addrlen) {
const auto size = std::min<size_t>(len, std::numeric_limits<int>::max());
return FinishSocketCall(static_cast<int>(Net::Send(s, buf, size, flags | 0x20000)));
return FinishSocketCall(
static_cast<int>(Net::Sendto(s, buf, size, flags | 0x20000, addr, addrlen)));
}
int KYTY_SYSV_ABI NetSend(int s, const void* buf, size_t len, int flags) {
return NetSendto(s, buf, len, flags, nullptr, 0);
}
int KYTY_SYSV_ABI NetRecv(int s, void* buf, size_t len, int flags) {
@@ -185,6 +199,12 @@ int KYTY_SYSV_ABI NetRecv(int s, void* buf, size_t len, int flags) {
return FinishSocketCall(static_cast<int>(Net::Recv(s, buf, size, flags)));
}
int KYTY_SYSV_ABI NetRecvfrom(int s, void* buf, size_t len, int flags, void* addr,
uint32_t* addrlen) {
const auto size = std::min<size_t>(len, std::numeric_limits<int>::max());
return FinishSocketCall(static_cast<int>(Net::Recvfrom(s, buf, size, flags, addr, addrlen)));
}
uint32_t KYTY_SYSV_ABI NetHtonl(uint32_t host32) {
return ((host32 & 0x000000ffu) << 24u) | ((host32 & 0x0000ff00u) << 8u) |
((host32 & 0x00ff0000u) >> 8u) | ((host32 & 0xff000000u) >> 24u);
@@ -209,6 +229,7 @@ LIB_DEFINE(InitNet_1_Net) {
LIB_FUNC("HQOwnfMGipQ", LibNet::GetNetErrorAddr);
LIB_FUNC("PIWqhn9oSxc", LibNet::NetAccept);
LIB_FUNC("bErx49PgxyY", LibNet::NetBind);
LIB_FUNC("OXXX4mUk3uk", LibNet::NetConnect);
LIB_FUNC("dgJBaeJnGpo", LibNet::NetPoolCreate);
LIB_FUNC("K7RlrTkI-mw", LibNet::NetPoolDestroy);
LIB_FUNC("C4UgDHHPvdw", LibNet::NetResolverCreate);
@@ -229,9 +250,12 @@ LIB_DEFINE(InitNet_1_Net) {
LIB_FUNC("TSM6whtekok", LibNet::NetShutdown);
LIB_FUNC("Q4qBuN-c0ZM", LibNet::NetSocket);
LIB_FUNC("45ggEzakPJQ", LibNet::NetSocketClose);
LIB_FUNC("xphrZusl78E", LibNet::NetGetsockopt);
LIB_FUNC("2mKX2Spso7I", LibNet::NetSetsockopt);
LIB_FUNC("beRjXBn-z+o", LibNet::NetSend);
LIB_FUNC("gvD1greCu0A", LibNet::NetSendto);
LIB_FUNC("9wO9XrMsNhc", LibNet::NetRecv);
LIB_FUNC("304ooNZxWDY", LibNet::NetRecvfrom);
LIB_FUNC("9T2pDF2Ryqg", LibNet::NetHtonl);
LIB_FUNC("iWQWrwiSt8A", LibNet::NetHtons);
LIB_FUNC("pQGpHYopAIY", LibNet::NetNtohl);
+84 -12
View File
@@ -992,6 +992,7 @@ static int ConvertHostSocketError(int error) {
case WSAENETUNREACH: posix_error = Posix::POSIX_ENETUNREACH; break;
case WSAENOTCONN: posix_error = Posix::POSIX_ENOTCONN; break;
case WSAENOTSOCK: posix_error = Posix::POSIX_ENOTSOCK; break;
case WSAENOPROTOOPT: posix_error = Posix::POSIX_ENOPROTOOPT; break;
case WSAEOPNOTSUPP: posix_error = Posix::POSIX_EOPNOTSUPP; break;
case WSAEPROTONOSUPPORT: posix_error = Posix::POSIX_EPROTONOSUPPORT; break;
case WSAESHUTDOWN: posix_error = Posix::POSIX_ESHUTDOWN; break;
@@ -1028,6 +1029,7 @@ static int ConvertHostSocketError(int error) {
case ENETUNREACH: posix_error = Posix::POSIX_ENETUNREACH; break;
case ENOTCONN: posix_error = Posix::POSIX_ENOTCONN; break;
case ENOTSOCK: posix_error = Posix::POSIX_ENOTSOCK; break;
case ENOPROTOOPT: posix_error = Posix::POSIX_ENOPROTOOPT; break;
case EOPNOTSUPP: posix_error = Posix::POSIX_EOPNOTSUPP; break;
case EPIPE: posix_error = Posix::POSIX_EPIPE; break;
case EPROTONOSUPPORT: posix_error = Posix::POSIX_EPROTONOSUPPORT; break;
@@ -1066,6 +1068,26 @@ static int ConvertSocketOptionLevel(int level) {
return (level == 0xffff ? SOL_SOCKET : level);
}
static int ConvertSocketOptionName(int level, int option) {
if (level == 0xffff) {
switch (option) {
case 0x0020: return SO_BROADCAST;
case 0x1001: return SO_SNDBUF;
case 0x1002: return SO_RCVBUF;
case 0x1007: return SO_ERROR;
case 0x1105: return SO_SNDTIMEO;
default: break;
}
} else if (level == 6 && option == 1) {
return TCP_NODELAY;
}
#if defined(_WIN32)
return option;
#else
return -1;
#endif
}
static int* P2pSocketOption(P2pEndpoint& endpoint, int option) {
switch (option) {
case 0x0020: return &endpoint.broadcast;
@@ -2149,21 +2171,41 @@ int KYTY_SYSV_ABI Getsockopt(int s, int level, int optname, void* optval, uint32
return 0;
}
// Guest socket options: SOL_SOCKET=0xffff, SO_ERROR=0x1007.
const bool socket_error = (level == 0xffff && optname == 0x1007);
#if !defined(_WIN32)
if (!socket_error) {
const bool send_timeout = (level == 0xffff && optname == 0x1105);
optname = ConvertSocketOptionName(level, optname);
if (optname < 0) {
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
}
#if defined(_WIN32)
DWORD timeout {};
#else
timeval timeout {};
#endif
if (socket_error) {
optname = SO_ERROR;
}
SocketLength len = static_cast<SocketLength>(*optlen);
if (::getsockopt(socket, ConvertSocketOptionLevel(level), optname, static_cast<char*>(optval),
void* value = optval;
if (send_timeout) {
if (*optlen < sizeof(int)) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
value = &timeout;
len = sizeof(timeout);
}
if (::getsockopt(socket, ConvertSocketOptionLevel(level), optname, static_cast<char*>(value),
&len) != 0) {
return SetHostSocketError();
}
if (send_timeout) {
#if defined(_WIN32)
const int64_t usec = static_cast<int64_t>(timeout) * 1000;
#else
const int64_t usec = static_cast<int64_t>(timeout.tv_sec) * 1'000'000 + timeout.tv_usec;
#endif
const int guest_timeout =
static_cast<int>(std::min<int64_t>(usec, std::numeric_limits<int>::max()));
std::memcpy(optval, &guest_timeout, sizeof(guest_timeout));
len = sizeof(guest_timeout);
}
if (socket_error && len >= static_cast<SocketLength>(sizeof(int))) {
auto* error = static_cast<int*>(optval);
*error = ConvertHostSocketError(*error);
@@ -2215,6 +2257,18 @@ int KYTY_SYSV_ABI Setsockopt(int s, int level, int optname, const void* optval,
return 0;
}
#if defined(__linux__)
if (level == 0xffff && optname == 0x10000) {
if (optlen != sizeof(int)) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
int enabled = 0;
std::memcpy(&enabled, optval, sizeof(enabled));
// Native sends preserve 255.255.255.255 without changing broadcast permission.
return enabled != 0 ? 0 : SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
}
#endif
constexpr int ORBIS_SO_NBIO = 0x1200;
if (ConvertSocketOptionLevel(level) == SOL_SOCKET && optname == ORBIS_SO_NBIO &&
optlen >= sizeof(int)) {
@@ -2230,14 +2284,32 @@ int KYTY_SYSV_ABI Setsockopt(int s, int level, int optname, const void* optval,
#endif
return failed ? SetHostSocketError() : 0;
}
#if !defined(_WIN32)
// Guest TCP options: IPPROTO_TCP=6, TCP_NODELAY=1.
if (level != 6 || optname != 1) {
const bool send_timeout = (level == 0xffff && optname == 0x1105);
optname = ConvertSocketOptionName(level, optname);
if (optname < 0) {
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
}
level = IPPROTO_TCP;
optname = TCP_NODELAY;
#if defined(_WIN32)
DWORD timeout {};
#else
timeval timeout {};
#endif
if (send_timeout) {
if (optlen != sizeof(int)) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
int usec = 0;
std::memcpy(&usec, optval, sizeof(usec));
usec = std::max(usec, 0);
#if defined(_WIN32)
timeout = static_cast<DWORD>((static_cast<int64_t>(usec) + 999) / 1000);
#else
timeout.tv_sec = usec / 1'000'000;
timeout.tv_usec = usec % 1'000'000;
#endif
optval = &timeout;
optlen = sizeof(timeout);
}
if (::setsockopt(socket, ConvertSocketOptionLevel(level), optname,
static_cast<const char*>(optval), static_cast<SocketLength>(optlen)) != 0) {
-30
View File
@@ -21,10 +21,8 @@
#include <set>
#include <unordered_set>
#include <vector>
#if !defined(__APPLE__)
#include <xbyak/xbyak.h>
#include <xbyak/xbyak_util.h>
#endif
#ifdef min
#undef min
@@ -33,9 +31,7 @@
#undef max
#endif
#if !defined(__APPLE__)
using namespace Xbyak::util;
#endif
namespace Loader {
@@ -52,8 +48,6 @@ using s64 = int64_t;
constexpr size_t NearJumpSize = 5;
#if !defined(__APPLE__)
struct PatchModule {
std::mutex mutex {};
u8* start = nullptr;
@@ -1573,19 +1567,6 @@ GuestInstructionPatchResult PatchGuestInstructions(u64 segment_addr, u64 segment
return result;
}
#else
GuestInstructionHostFeatures GetGuestInstructionHostFeatures() {
return {};
}
GuestInstructionPatchResult PatchGuestInstructions(u64, u64, std::span<const uintptr_t>, bool, bool,
GuestInstructionHostFeatures) {
return {};
}
#endif
namespace {
constexpr uint8_t DW_EH_PE_FORMAT_MASK = 0x0f;
@@ -1763,7 +1744,6 @@ bool DecodeEhFrameFunctionStarts(uint64_t eh_frame_header_addr, uint64_t eh_fram
void RegisterGuestInstructionPatchModule(void* module_ptr, uint64_t module_size,
void* trampoline_area_ptr, uint64_t trampoline_area_size) {
#if !defined(__APPLE__)
EXIT_IF(module_ptr == nullptr || module_size == 0 || trampoline_area_ptr == nullptr ||
trampoline_area_size == 0);
const auto module_addr = reinterpret_cast<u64>(module_ptr);
@@ -1772,20 +1752,10 @@ void RegisterGuestInstructionPatchModule(void* module_ptr, uint64_t module_size,
std::forward_as_tuple(static_cast<u8*>(module_ptr), module_size,
static_cast<u8*>(trampoline_area_ptr),
trampoline_area_size));
#else
(void)module_ptr;
(void)module_size;
(void)trampoline_area_ptr;
(void)trampoline_area_size;
#endif
}
void UnregisterGuestInstructionPatchModule(void* module_ptr) {
#if !defined(__APPLE__)
g_patch_modules.erase(reinterpret_cast<u64>(module_ptr));
#else
(void)module_ptr;
#endif
}
#undef ASSERT
-4
View File
@@ -1670,11 +1670,7 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
uint64_t tls_handler_size = is_shared ? 0 : Jit::SafeCall::GetSize();
EXIT_IF(tls_handler_size > UINT64_MAX - program->base_size_aligned);
program->mapped_size = program->base_size_aligned + tls_handler_size;
#if !defined(__APPLE__)
const bool emulate_amd = Config::AmdCpuEnabled();
#else
const bool emulate_amd = false;
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
const bool protect_memory_faults = Config::RedZoneProtectionEnabled();
#else
+15 -8
View File
@@ -12,6 +12,7 @@
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#include <windows.h> // IWYU pragma: keep
#elif defined(__APPLE__)
#include <sched.h>
#include <sys/ucontext.h>
#else
#include <sched.h>
@@ -468,6 +469,16 @@ struct Context {
default: return nullptr;
}
}
// The kernel hands AVX threads the longer context that carries YMM[255:128]; a plain SSE
// context has no such state to clear.
void ClearUpperYmm(uint8_t index) const {
if (index >= 16 || native->uc_mcsize < sizeof(_STRUCT_MCONTEXT_AVX64)) {
return;
}
auto* avx = reinterpret_cast<_STRUCT_MCONTEXT_AVX64*>(native->uc_mcontext);
auto* ymmh = &avx->__fs.__fpu_ymmh0;
ymmh[index] = {};
}
#else
ucontext_t* native;
@@ -703,8 +714,6 @@ static bool TryEmulateCpuExtensions(Context& context) {
return true;
}
#if !defined(__APPLE__)
static bool TryEmulateMonitorxMwaitx(Context& context) {
const auto* rip = reinterpret_cast<const uint8_t*>(context.Rip());
if (rip[0] != 0x0f || rip[1] != 0x01 || (rip[2] != 0xfa && rip[2] != 0xfb)) {
@@ -790,8 +799,6 @@ static bool TryEmulateReciprocalSquareRoot(Context& context) {
return true;
}
#endif
bool TryEmulate(void* native_context) {
if (native_context == nullptr) {
return false;
@@ -807,12 +814,12 @@ bool TryEmulate(void* native_context) {
#else
Context context {static_cast<ucontext_t*>(native_context)};
#endif
#if !defined(__APPLE__)
return TryEmulateReciprocalSquareRoot(context) || TryEmulateMonitorxMwaitx(context) ||
TryEmulateSse4a(context) || TryEmulateShaNi(context) || TryEmulateCpuExtensions(context);
#else
return TryEmulateSse4a(context) || TryEmulateCpuExtensions(context);
TryEmulateSse4a(context) ||
#if !defined(__APPLE__)
TryEmulateShaNi(context) ||
#endif
TryEmulateCpuExtensions(context);
}
} // namespace Loader::X64InstructionEmulator
+163 -5
View File
@@ -34,6 +34,11 @@
#include <thread>
#include <vector>
#if defined(__linux__)
#include <sys/mman.h>
#include <unistd.h>
#endif
namespace Libs::LibKernelApr {
void InitLibKernel_1_Apr(Loader::SymbolDatabase *symbols);
}
@@ -1061,19 +1066,40 @@ void CheckSocketWakeup() {
namespace Net = Libs::Network::Net;
Loader::SymbolDatabase symbols;
Libs::LibNet::InitNet_1_Net(&symbols);
const auto *connect_symbol = symbols.Find(
{"OXXX4mUk3uk", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
const auto *getsockopt_symbol = symbols.Find(
{"xphrZusl78E", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
const auto *setsockopt_symbol = symbols.Find(
{"2mKX2Spso7I", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
const auto *send_symbol = symbols.Find(
{"beRjXBn-z+o", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
const auto *sendto_symbol = symbols.Find(
{"gvD1greCu0A", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
const auto *recv_symbol = symbols.Find(
{"9wO9XrMsNhc", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
const auto *recvfrom_symbol = symbols.Find(
{"304ooNZxWDY", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
const auto *errno_symbol = symbols.Find(
{"HQOwnfMGipQ", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
Check(send_symbol && recv_symbol && errno_symbol,
"Net send, receive and errno exports resolve with the guest ABI versions");
Check(connect_symbol && getsockopt_symbol && setsockopt_symbol && send_symbol && sendto_symbol &&
recv_symbol && recvfrom_symbol && errno_symbol,
"Net socket and errno exports resolve with the guest ABI versions");
using Connect = int (KYTY_SYSV_ABI *)(int, const void *, uint32_t);
using Getsockopt = int (KYTY_SYSV_ABI *)(int, int, int, void *, uint32_t *);
using Setsockopt = int (KYTY_SYSV_ABI *)(int, int, int, const void *, uint32_t);
using Send = int (KYTY_SYSV_ABI *)(int, const void *, size_t, int);
using Sendto = int (KYTY_SYSV_ABI *)(int, const void *, size_t, int, const void *, uint32_t);
using Recv = int (KYTY_SYSV_ABI *)(int, void *, size_t, int);
using Recvfrom = int (KYTY_SYSV_ABI *)(int, void *, size_t, int, void *, uint32_t *);
using Errno = int *(KYTY_SYSV_ABI *)();
const auto net_connect = reinterpret_cast<Connect>(connect_symbol->vaddr);
const auto net_getsockopt = reinterpret_cast<Getsockopt>(getsockopt_symbol->vaddr);
const auto net_setsockopt = reinterpret_cast<Setsockopt>(setsockopt_symbol->vaddr);
const auto net_send = reinterpret_cast<Send>(send_symbol->vaddr);
const auto net_sendto = reinterpret_cast<Sendto>(sendto_symbol->vaddr);
const auto net_recv = reinterpret_cast<Recv>(recv_symbol->vaddr);
const auto net_recvfrom = reinterpret_cast<Recvfrom>(recvfrom_symbol->vaddr);
auto *net_errno = reinterpret_cast<Errno>(errno_symbol->vaddr)();
const auto [reader, writer] = CreateTcpPair();
const int enabled = 1;
@@ -1082,10 +1108,15 @@ void CheckSocketWakeup() {
int socket_error = -1;
uint32_t error_size = sizeof(socket_error);
*Libs::Posix::GetErrorAddr() = Libs::Posix::POSIX_EINVAL;
Check(Net::Getsockopt(writer, 0xffff, 0x1007, &socket_error, &error_size) == 0 &&
*net_errno = Libs::Posix::POSIX_EINVAL;
Check(net_getsockopt(writer, 0xffff, 0x1007, &socket_error, &error_size) == 0 &&
socket_error == 0 && error_size == sizeof(socket_error) &&
*net_errno == Libs::Posix::POSIX_EINVAL &&
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EINVAL,
"SO_ERROR reports socket status without changing guest errno");
"Net SO_ERROR reports socket status without changing either guest errno");
Check(net_getsockopt(writer, 0xffff, 0x1007, nullptr, &error_size) ==
Libs::Network::NET_ERROR_EFAULT && *net_errno == Libs::Posix::POSIX_EFAULT,
"Net getsockopt translates an invalid output buffer");
std::array<uint64_t, 16> readable {};
const auto bit = uint64_t {1} << (reader % 64);
@@ -1122,6 +1153,131 @@ void CheckSocketWakeup() {
*net_errno == Libs::Posix::POSIX_EWOULDBLOCK,
"Net nonblocking receive translates POSIX failure and Net errno");
#endif
const int datagram = Net::Socket(2, 2, 0);
const int datagram_writer = Net::Socket(2, 2, 0);
std::array<uint8_t, 16> address {16, 2, 0, 0, 127, 0, 0, 1};
std::array<uint8_t, 16> peer {}, expected_peer {};
uint32_t address_size = address.size(), peer_size = peer.size();
uint32_t expected_peer_size = expected_peer.size();
Check(datagram >= 0 && datagram_writer >= 0 &&
Net::Bind(datagram, address.data(), address.size()) == 0 &&
Net::Bind(datagram_writer, address.data(), address.size()) == 0 &&
Net::Getsockname(datagram, address.data(), &address_size) == 0 &&
Net::Getsockname(datagram_writer, expected_peer.data(), &expected_peer_size) == 0 &&
Net::Setsockopt(datagram, 0xffff, 0x1200, &enabled, sizeof(enabled)) == 0,
"create nonblocking loopback datagrams for Net ABI verification");
*net_errno = Libs::Posix::POSIX_EINVAL;
*Libs::Posix::GetErrorAddr() = Libs::Posix::POSIX_EINVAL;
for (const int option : {0x1001, 0x1002}) {
constexpr int requested = 16384;
int actual = 0;
uint32_t size = sizeof(actual);
Check(net_setsockopt(datagram, 0xffff, option, &requested, sizeof(requested)) == 0 &&
net_getsockopt(datagram, 0xffff, option, &actual, &size) == 0 &&
actual >= requested && size == sizeof(actual),
"Net UDP send and receive buffers accept guest option numbers");
}
for (const int value : {1, 0}) {
int actual = -1;
uint32_t size = sizeof(actual);
Check(net_setsockopt(datagram, 0xffff, 0x20, &value, sizeof(value)) == 0 &&
net_getsockopt(datagram, 0xffff, 0x20, &actual, &size) == 0 &&
actual == value && size == sizeof(actual),
"Net UDP broadcast option can be enabled and disabled");
}
int timeout = 0;
int *timeout_value = &timeout;
#if defined(__linux__)
int broadcast = -1;
uint32_t broadcast_size = sizeof(broadcast);
Check(net_setsockopt(datagram, 0xffff, 0x10000, &enabled, sizeof(enabled)) == 0 &&
net_getsockopt(datagram, 0xffff, 0x20, &broadcast, &broadcast_size) == 0 &&
broadcast == 0,
"preserving the all-ones destination does not enable broadcast permission");
const long page_size = sysconf(_SC_PAGESIZE);
Check(page_size > 0, "get host page size for socket timeout boundary");
void *timeout_pages = mmap(nullptr, page_size * 2, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Check(timeout_pages != MAP_FAILED &&
mprotect(static_cast<char *>(timeout_pages) + page_size, page_size,
PROT_NONE) == 0,
"guard memory after the four-byte socket timeout");
timeout_value = reinterpret_cast<int *>(static_cast<char *>(timeout_pages) +
page_size - sizeof(int));
#endif
for (const int value : {1500000, 0, -1}) {
*timeout_value = value;
Check(net_setsockopt(datagram, 0xffff, 0x1105, timeout_value, sizeof(int)) == 0,
"Net send timeout reads a four-byte microsecond value");
*timeout_value = -2;
uint32_t size = sizeof(int);
Check(net_getsockopt(datagram, 0xffff, 0x1105, timeout_value, &size) == 0 &&
*timeout_value == std::max(value, 0) && size == sizeof(int),
"Net send timeout returns four-byte microseconds and disables nonpositive values");
}
#if defined(__linux__)
Check(munmap(timeout_pages, page_size * 2) == 0, "free socket timeout guard pages");
#endif
Check(*net_errno == Libs::Posix::POSIX_EINVAL &&
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EINVAL,
"successful Net socket option calls preserve both guest errno values");
Check(net_setsockopt(datagram, 0xffff, 0x1105, nullptr, sizeof(timeout)) ==
Libs::Network::NET_ERROR_EFAULT && *net_errno == Libs::Posix::POSIX_EFAULT &&
net_getsockopt(datagram, 0xffff, 0x1105, &timeout, nullptr) ==
Libs::Network::NET_ERROR_EFAULT && *net_errno == Libs::Posix::POSIX_EFAULT,
"Net socket options reject null value and length pointers");
uint32_t short_size = sizeof(timeout) - 1;
Check(net_setsockopt(datagram, 0xffff, 0x1105, &timeout, short_size) ==
Libs::Network::NET_ERROR_EINVAL && *net_errno == Libs::Posix::POSIX_EINVAL &&
net_getsockopt(datagram, 0xffff, 0x1105, &timeout, &short_size) ==
Libs::Network::NET_ERROR_EINVAL && *net_errno == Libs::Posix::POSIX_EINVAL,
"Net send timeout rejects undersized values");
uint32_t option_size = sizeof(timeout);
Check(net_setsockopt(datagram, 0xffff, 0x7fffffff, &timeout, option_size) ==
Libs::Network::NET_ERROR_ENOPROTOOPT &&
*net_errno == Libs::Posix::POSIX_ENOPROTOOPT &&
net_getsockopt(datagram, 0xffff, 0x7fffffff, &timeout, &option_size) ==
Libs::Network::NET_ERROR_ENOPROTOOPT &&
*net_errno == Libs::Posix::POSIX_ENOPROTOOPT,
"Net unknown socket options return protocol-option errors");
#if defined(__linux__)
Check(net_setsockopt(datagram, 0xffff, 0x1007, &timeout, option_size) ==
Libs::Network::NET_ERROR_ENOPROTOOPT &&
*net_errno == Libs::Posix::POSIX_ENOPROTOOPT,
"Net native protocol-option errors retain their guest error code");
#endif
received.fill(0);
*net_errno = Libs::Posix::POSIX_EINVAL;
Check(net_sendto(datagram_writer, payload, sizeof(payload), 0,
address.data(), address_size) == sizeof(payload) &&
*net_errno == Libs::Posix::POSIX_EINVAL,
"Net sendto delivers to a guest sockaddr and preserves errno on success");
Check(net_recvfrom(datagram, received.data(), received.size(), 0,
peer.data(), &peer_size) == sizeof(payload) &&
std::memcmp(received.data(), payload, sizeof(payload)) == 0 &&
peer_size == expected_peer_size && peer == expected_peer &&
*net_errno == Libs::Posix::POSIX_EINVAL,
"Net recvfrom returns datagram bytes and sender address while preserving errno");
Check(net_recvfrom(datagram, received.data(), received.size(), 0, nullptr, nullptr) ==
Libs::Network::NET_ERROR_EWOULDBLOCK &&
*net_errno == Libs::Posix::POSIX_EWOULDBLOCK,
"Net recvfrom translates nonblocking failure with an omitted sender address");
*net_errno = Libs::Posix::POSIX_EINVAL;
Check(net_connect(datagram_writer, address.data(), address_size) == 0 &&
*net_errno == Libs::Posix::POSIX_EINVAL &&
net_sendto(datagram_writer, payload, sizeof(payload), 0, nullptr, 0) ==
sizeof(payload) && *net_errno == Libs::Posix::POSIX_EINVAL &&
net_recvfrom(datagram, received.data(), received.size(), 0, nullptr, nullptr) ==
sizeof(payload) && std::memcmp(received.data(), payload, sizeof(payload)) == 0,
"Net connect selects the peer used by sendto with an omitted destination");
Check(net_connect(-1, address.data(), address_size) == Libs::Network::NET_ERROR_EBADF &&
*net_errno == Libs::Posix::POSIX_EBADF,
"Net connect translates an invalid socket");
Check(net_sendto(-1, payload, sizeof(payload), 0, address.data(), address_size) ==
Libs::Network::NET_ERROR_EBADF && *net_errno == Libs::Posix::POSIX_EBADF,
"Net sendto translates an invalid socket");
Check(Net::SocketClose(datagram) == 0 && Net::SocketClose(datagram_writer) == 0,
"close Net loopback datagrams");
Check(net_send(-1, payload, sizeof(payload), 0) == Libs::Network::NET_ERROR_EBADF &&
*net_errno == Libs::Posix::POSIX_EBADF,
"Net send translates an invalid socket instead of returning POSIX minus one");
@@ -1138,10 +1294,12 @@ void CheckSocketWakeup() {
const auto previous_sigpipe = std::signal(SIGPIPE, SIG_DFL);
Check(previous_sigpipe != SIG_ERR, "set default SIGPIPE disposition for Net send");
const auto broken_send = net_send(disconnected, payload, sizeof(payload), 0);
const auto broken_sendto = net_sendto(disconnected, payload, sizeof(payload), 0, nullptr, 0);
std::signal(SIGPIPE, previous_sigpipe);
Check(broken_send == Libs::Network::NET_ERROR_EPIPE &&
broken_sendto == Libs::Network::NET_ERROR_EPIPE &&
*net_errno == Libs::Posix::POSIX_EPIPE,
"Net send reports a broken pipe without raising host SIGPIPE");
"Net send and sendto report a broken pipe without raising host SIGPIPE");
Check(Net::SocketClose(disconnected) == 0, "close unconnected socket");
#endif
#if defined(_WIN32)
+2 -1
View File
@@ -33541,7 +33541,8 @@ void CheckIndirectBufferStore(VulkanHarness &vulkan) {
candidate.indirect_search_iterations = 0;
candidate.indirect_resources.clear();
program.descriptor_sources.resize(1);
program.descriptor_sources[0].indirect_descriptor.emplace().table_stride = 16;
program.descriptor_sources[0].indirect_descriptor =
DescriptorSource::IndirectDescriptor{.table_stride = 16};
ShaderComputeInputInfo compute;
compute.wave_size = 32;
compute.host_subgroup_size = vulkan.SubgroupSize();
+227
View File
@@ -1,6 +1,8 @@
#include "common/hostException.h"
#include "loader/guestInstructionPatcher.h"
#include "loader/x64InstructionEmulator.h"
#include <array>
#include <atomic>
#include <bit>
#include <cstddef>
@@ -82,6 +84,53 @@ constexpr uint8_t kUd2[] = {
0xc3, // ret
};
// VRSQRTPS xmm1, xmm0 as the AMD CPU patcher leaves it when it cannot relocate the instruction: a
// reserved VEX.vvvv bit is cleared (c5 f8 -> c5 f0), which raises #UD on every host. Rosetta aborted
// the guest on exactly this encoding until the emulator handled it there too.
constexpr uint8_t kTrappedVrsqrtpsXmm1Xmm0[] = {
0x0f, 0x10, 0x06, // movups (%rsi), %xmm0
0xc5, 0xfc, 0x10, 0x0e, // vmovups ymm1, [rsi]: dirty the upper half of ymm1
0xc5, 0xf0, 0x52, 0xc8, // vrsqrtps xmm1, xmm0 with the vvvv trap mark
0x0f, 0x11, 0x0f, // movups %xmm1, (%rdi)
0xc4, 0xe3, 0x7d, 0x19, 0x4f, 0x10, 0x01, // vextractf128 [rdi + 16], ymm1, 1
0xc5, 0xf8, 0x77, // vzeroupper
0xc3, // ret
};
// The same sequence with the ordinary VEX.128 VRSQRTPS encoding (vvvv = 1111), as it appears in guest
// code before the patcher marks it as a trap.
constexpr uint8_t kPlainVrsqrtpsXmm1Xmm0[] = {
0x0f, 0x10, 0x06, // movups (%rsi), %xmm0
0xc5, 0xfc, 0x10, 0x0e, // vmovups ymm1, [rsi]: dirty the upper half of ymm1
0xc5, 0xf8, 0x52, 0xc8, // vrsqrtps xmm1, xmm0
0x0f, 0x11, 0x0f, // movups %xmm1, (%rdi)
0xc4, 0xe3, 0x7d, 0x19, 0x4f, 0x10, 0x01, // vextractf128 [rdi + 16], ymm1, 1
0xc5, 0xf8, 0x77, // vzeroupper
0xc3, // ret
};
// The same again behind a never taken indirect branch. A function with an indirect branch does not
// let the patcher relocate neighbours, and the 4-byte VRSQRTPS cannot hold a jump on its own, so it
// is left in place with the trap mark.
constexpr uint8_t kPlainVrsqrtpsIndirectBranch[] = {
0x31, 0xc0, // xor %eax, %eax
0x85, 0xc0, // test %eax, %eax
0x74, 0x02, // jz over
0xff, 0xe0, // jmp *%rax (never taken)
0x0f, 0x10, 0x06, // movups (%rsi), %xmm0
0xc5, 0xfc, 0x10, 0x0e, // vmovups ymm1, [rsi]: dirty the upper half of ymm1
0xc5, 0xf8, 0x52, 0xc8, // vrsqrtps xmm1, xmm0
0x0f, 0x11, 0x0f, // movups %xmm1, (%rdi)
0xc4, 0xe3, 0x7d, 0x19, 0x4f, 0x10, 0x01, // vextractf128 [rdi + 16], ymm1, 1
0xc5, 0xf8, 0x77, // vzeroupper
0xc3, // ret
};
constexpr uint8_t kMwaitx[] = {
0x0f, 0x01, 0xfb, // mwaitx
0xc3, // ret
};
constexpr size_t kPageSize = 4096;
std::atomic<int> g_illegal_hits {0};
@@ -233,6 +282,13 @@ void TestInsertqRegister() {
}
}
void TestMwaitxEmulated() {
auto fn = MapCode<VoidBlobFn>(kMwaitx);
const int before = IllegalHits();
fn();
Check(IllegalHits() > before, "host raises SIGILL for MWAITX");
}
void TestUnknownInstructionRefused() {
g_refused.store(false, std::memory_order_relaxed);
g_refuse_skip.store(2, std::memory_order_relaxed);
@@ -244,6 +300,170 @@ void TestUnknownInstructionRefused() {
g_refuse_skip.store(0, std::memory_order_relaxed);
}
// The loader rewrites SSE4a instructions into native trampolines when the patcher is enabled. The
// trampoline area lives right behind the code page, like it does behind a mapped guest module.
struct PatchedCode {
void *code = nullptr;
Loader::GuestInstructionPatchResult result {};
};
template <size_t N>
PatchedCode MapPatchedCode(const uint8_t (&blob)[N], bool emulate_amd) {
void *area = ::mmap(nullptr, 2 * kPageSize, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANON, -1, 0);
Check(area != MAP_FAILED, "map RWX code and trampoline area");
std::memcpy(area, blob, N);
auto *trampoline = static_cast<uint8_t *>(area) + kPageSize;
Loader::RegisterGuestInstructionPatchModule(area, kPageSize, trampoline, kPageSize);
const std::array<uintptr_t, 1> function_starts {reinterpret_cast<uintptr_t>(area)};
// A host without SSE4a, RDPID and CLWB, which is what Rosetta presents.
const Loader::GuestInstructionHostFeatures host {false, false, false};
PatchedCode patched;
patched.code = area;
patched.result = Loader::PatchGuestInstructions(reinterpret_cast<uintptr_t>(area), N, function_starts,
false, emulate_amd, host);
Loader::UnregisterGuestInstructionPatchModule(area);
return patched;
}
using PatchCounts = Loader::InstructionPatchCounts Loader::GuestInstructionPatchResult::*;
// Runs the blob through the SIGILL emulator (reference) and through the patched copy, which must
// produce the same bytes. EXTRQ becomes a native trampoline without a single SIGILL. INSERTQ is
// marked as a trap by the patcher and keeps going through the signal emulator.
template <typename Fn, size_t N, typename Invoke>
void CheckPatchedMatchesEmulator(const uint8_t (&blob)[N], PatchCounts counts, bool native,
Invoke invoke, const char *text) {
uint64_t reference[2] {};
const int before_reference = IllegalHits();
invoke(MapCode<Fn>(blob), reference);
Check(IllegalHits() > before_reference, text);
const auto unpatched = MapPatchedCode(blob, false);
Check((unpatched.result.*counts).found == 0, "patcher stays off without emulate_amd");
uint64_t unpatched_out[2] {};
const int before_off = IllegalHits();
invoke(reinterpret_cast<Fn>(unpatched.code), unpatched_out);
Check(IllegalHits() > before_off, "unpatched copy still raises SIGILL");
const auto patched = MapPatchedCode(blob, true);
Check((patched.result.*counts).found == 1, text);
Check((patched.result.*counts).native == (native ? 1u : 0u), "native trampoline count");
Check((patched.result.*counts).trapped == (native ? 0u : 1u), "trapped (signal path) count");
uint64_t out[2] {};
const int before = IllegalHits();
invoke(reinterpret_cast<Fn>(patched.code), out);
if (native) {
Check(IllegalHits() == before, "patched code raises no SIGILL");
} else {
Check(IllegalHits() > before, "trapped instruction still goes through the signal emulator");
}
Check(out[0] == reference[0] && out[1] == reference[1], "patched result equals emulated result");
}
void TestPatchedExtrqImmediate() {
CheckPatchedMatchesEmulator<BlobFn>(
kExtrqXmm2Len40Index0, &Loader::GuestInstructionPatchResult::extrq, true,
[](BlobFn fn, uint64_t *out) { fn(out, 0.0, 0.0, std::bit_cast<double>(0xaabbccddeeff1122ull)); },
"EXTRQ immediate found by the patcher");
}
void TestPatchedExtrqRexHigh() {
CheckPatchedMatchesEmulator<LoadBlobFn>(
kExtrqXmm10Len40Index0, &Loader::GuestInstructionPatchResult::extrq, true,
[](LoadBlobFn fn, uint64_t *out) {
const uint64_t in[2] {0xaabbccddeeff1122ull, 0x5555555555555555ull};
fn(out, in);
},
"REX EXTRQ found by the patcher");
}
void TestPatchedInsertqImmediate() {
CheckPatchedMatchesEmulator<BlobFn>(
kInsertqXmm2Xmm0Len32Index0, &Loader::GuestInstructionPatchResult::insertq, false,
[](BlobFn fn, uint64_t *out) {
fn(out, std::bit_cast<double>(0x1122334455667788ull), 0.0,
std::bit_cast<double>(0x0123456789abcdefull));
},
"INSERTQ immediate found by the patcher");
}
void TestPatchedInsertqIndexedRexHigh() {
CheckPatchedMatchesEmulator<PairBlobFn>(
kInsertqXmm10Xmm9Len16Index8, &Loader::GuestInstructionPatchResult::insertq, false,
[](PairBlobFn fn, uint64_t *out) {
const uint64_t src[2] {0x8877665544332211ull, 0xdeadbeefcafebabeull};
const uint64_t dest[2] {0xaaaaaaaaaaaaaaaaull, 0xbbbbbbbbbbbbbbbbull};
fn(out, src, dest);
},
"indexed REX INSERTQ found by the patcher");
}
void TestPatchedInsertqRegister() {
CheckPatchedMatchesEmulator<PairBlobFn>(
kInsertqXmm10Xmm9, &Loader::GuestInstructionPatchResult::insertq, false,
[](PairBlobFn fn, uint64_t *out) {
const uint64_t src[2] {0x0123456789abcdefull, 0xdeadbeef0000c4c8ull};
const uint64_t dest[2] {0x8877665544332211ull, 0xfedcba9876543210ull};
fn(out, src, dest);
},
"register INSERTQ found by the patcher");
}
// The ordinary VEX.128 VRSQRTPS must be found by the patcher and give the reference result, either
// through a native trampoline (no SIGILL) or, when it cannot be relocated, marked as a trap (the
// vvvv bit is cleared in place) and emulated from the signal handler.
template <size_t N>
void CheckPatchedReciprocalSquareRoot(const uint8_t (&blob)[N], bool native) {
const uint32_t in_bits[8] {std::bit_cast<uint32_t>(4.0f), 0x00000000u, 0xbf800000u, 0x7f800000u,
0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
const auto Run = [&](LoadBlobFn fn, uint32_t (&out_bits)[8]) {
fn(reinterpret_cast<uint64_t *>(out_bits), reinterpret_cast<const uint64_t *>(in_bits));
};
uint32_t reference[8] {};
const int before_reference = IllegalHits();
Run(MapCode<LoadBlobFn>(kTrappedVrsqrtpsXmm1Xmm0), reference);
Check(IllegalHits() > before_reference, "host raises SIGILL for the pre-marked VRSQRTPS");
const auto unpatched = MapPatchedCode(blob, false);
Check(unpatched.result.reciprocal_sqrt.found == 0, "patcher leaves VRSQRTPS alone without emulate_amd");
const auto patched = MapPatchedCode(blob, true);
const auto &counts = patched.result.reciprocal_sqrt;
Check(counts.found == 1, "patcher finds the ordinary VEX.128 VRSQRTPS");
Check(counts.native == (native ? 1u : 0u) && counts.trapped == (native ? 0u : 1u),
"VRSQRTPS native trampoline / trap count");
const auto *code = static_cast<const uint8_t *>(patched.code);
size_t site = 0;
while (site + 2 < N && !(blob[site] == 0xc5 && blob[site + 1] == 0xf8 && blob[site + 2] == 0x52)) {
++site;
}
Check(site + 2 < N, "locate VRSQRTPS in the test blob");
if (!native) {
Check(code[site] == 0xc5 && code[site + 1] == 0xf0 && code[site + 2] == 0x52,
"the trap mark clears a reserved VEX.vvvv bit (c5 f8 -> c5 f0)");
} else {
Check(code[site] != 0xc5 || code[site + 1] != 0xf8, "native trampoline replaced the VRSQRTPS");
}
uint32_t out[8] {};
const int before = IllegalHits();
Run(reinterpret_cast<LoadBlobFn>(patched.code), out);
if (native) {
Check(IllegalHits() == before, "natively patched VRSQRTPS raises no SIGILL");
} else {
Check(IllegalHits() > before, "marked VRSQRTPS goes through the signal emulator");
}
Check(std::memcmp(out, reference, sizeof(out)) == 0, "patched VRSQRTPS result equals the emulated reference");
Check(std::bit_cast<float>(out[0]) == 0.5f && out[1] == 0x7f800000u && out[2] == 0xffc00000u && out[3] == 0,
"patched VRSQRTPS produces the exact reciprocal square roots");
Check(out[4] == 0 && out[5] == 0 && out[6] == 0 && out[7] == 0, "patched VEX.128 form zeroes the upper half");
}
void TestPatchedReciprocalSquareRoot() {
CheckPatchedReciprocalSquareRoot(kPlainVrsqrtpsXmm1Xmm0, true);
CheckPatchedReciprocalSquareRoot(kPlainVrsqrtpsIndirectBranch, false);
}
} // namespace
int main() {
@@ -255,7 +475,14 @@ int main() {
TestRexHighRegisterExtrq();
TestRexHighRegisterInsertqWithIndex();
TestInsertqRegister();
TestMwaitxEmulated();
TestUnknownInstructionRefused();
TestPatchedExtrqImmediate();
TestPatchedExtrqRexHigh();
TestPatchedInsertqImmediate();
TestPatchedInsertqIndexedRexHigh();
TestPatchedInsertqRegister();
TestPatchedReciprocalSquareRoot();
std::printf("macosSse4aTests: all passed\n");
return 0;
}