[INTERP][DYNAREC] Fixed XSAVE/XRSTOR opcodes (#3849)

* [INTERP][DYNAREC] Fixed XSAVE/XRSTOR opcodes

* Added a unitest
This commit is contained in:
Yang Liu authored and GitHub committed 2026-05-12 10:13:02 +02:00
1 parent 3b82d5a2c2
commit ce3af2bd7a
8 files changed
+175 -8

No files matched your search

+5
View File
@@ -1887,6 +1887,11 @@ add_test(lock ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64}
endif()
if(NOT ANDROID)
add_test(NAME xsave_xrstor COMMAND ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64}
-D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/misc/xsave_xrstor -D TEST_OUTPUT=tmpfile-xsave_xrstor.txt
-D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/misc/xsave_xrstor.txt
-P ${CMAKE_SOURCE_DIR}/runTest.cmake)
file(GLOB extension_tests "${CMAKE_SOURCE_DIR}/tests/extensions/*.c")
foreach(file ${extension_tests})
get_filename_component(testname "${file}" NAME_WE)
+2 -2
View File
@@ -2075,7 +2075,7 @@ uintptr_t dynarec64_0F(dynarec_arm_t* dyn, uintptr_t addr, uintptr_t ip, int nin
BARRIER(BARRIER_FLOAT);
addr = geted(dyn, addr, ninst, nextop, &ed, x1, &fixedaddress, NULL, 0, 0, rex, NULL, 0, 0);
if(ed!=x1) {MOVx_REG(x1, ed);}
MOV32w(x2, rex.w?0:1);
MOV32w(x2, rex.is32bits);
CALL(const_fpu_xsave, -1);
break;
case 5:
@@ -2084,7 +2084,7 @@ uintptr_t dynarec64_0F(dynarec_arm_t* dyn, uintptr_t addr, uintptr_t ip, int nin
BARRIER(BARRIER_FLOAT);
addr = geted(dyn, addr, ninst, nextop, &ed, x1, &fixedaddress, NULL, 0, 0, rex, NULL, 0, 0);
if(ed!=x1) {MOVx_REG(x1, ed);}
MOV32w(x2, rex.w?0:1);
MOV32w(x2, rex.is32bits);
CALL(const_fpu_xrstor, -1);
break;
case 7:
+2 -2
View File
@@ -1709,7 +1709,7 @@ uintptr_t dynarec64_0F(dynarec_la64_t* dyn, uintptr_t addr, uintptr_t ip, int ni
MESSAGE(LOG_DUMP, "Need Optimization\n");
BARRIER(BARRIER_FLOAT);
addr = geted(dyn, addr, ninst, nextop, &ed, x1, x2, &fixedaddress, rex, NULL, 0, 0);
MOV32w(x2, rex.w ? 0 : 1);
MOV32w(x2, rex.is32bits);
CALL(const_fpu_xsave, -1, ed, x2);
break;
case 5:
@@ -1717,7 +1717,7 @@ uintptr_t dynarec64_0F(dynarec_la64_t* dyn, uintptr_t addr, uintptr_t ip, int ni
MESSAGE(LOG_DUMP, "Need Optimization\n");
BARRIER(BARRIER_FLOAT);
addr = geted(dyn, addr, ninst, nextop, &ed, x1, x2, &fixedaddress, rex, NULL, 0, 0);
MOV32w(x2, rex.w ? 0 : 1);
MOV32w(x2, rex.is32bits);
CALL(const_fpu_xrstor, -1, ed, x2);
break;
case 7:
+2 -2
View File
@@ -2095,7 +2095,7 @@ uintptr_t dynarec64_0F(dynarec_rv64_t* dyn, uintptr_t addr, uintptr_t ip, int ni
MESSAGE(LOG_DUMP, "Need Optimization\n");
fpu_purgecache(dyn, ninst, 0, x1, x2, x3);
addr = geted(dyn, addr, ninst, nextop, &ed, x1, x2, &fixedaddress, rex, NULL, 0, 0);
MOV32w(x2, rex.w ? 0 : 1);
MOV32w(x2, rex.is32bits);
CALL(const_fpu_xsave, -1, ed, x2);
break;
case 5:
@@ -2103,7 +2103,7 @@ uintptr_t dynarec64_0F(dynarec_rv64_t* dyn, uintptr_t addr, uintptr_t ip, int ni
MESSAGE(LOG_DUMP, "Need Optimization\n");
fpu_purgecache(dyn, ninst, 0, x1, x2, x3);
addr = geted(dyn, addr, ninst, nextop, &ed, x1, x2, &fixedaddress, rex, NULL, 0, 0);
MOV32w(x2, rex.w ? 0 : 1);
MOV32w(x2, rex.is32bits);
CALL(const_fpu_xrstor, -1, ed, x2);
break;
case 7:
+2 -2
View File
@@ -1403,12 +1403,12 @@ uintptr_t Run0F(x64emu_t *emu, rex_t rex, uintptr_t addr, int *step)
#ifdef TEST_INTERPRETER
emu->sw.f.F87_TOP = emu->top&7;
#else
fpu_xsave(emu, ED, rex.w?0:1);
fpu_xsave(emu, ED, rex.is32bits);
#endif
break;
case 5: /* XRSTOR Ed */
_GETED(0);
fpu_xrstor(emu, ED, rex.w?0:1);
fpu_xrstor(emu, ED, rex.is32bits);
break;
case 7: /* CLFLUSH Ed */
_GETED(0);
BIN
View File
Binary file not shown.
+160
View File
@@ -0,0 +1,160 @@
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
typedef struct xmm128_s {
uint64_t q[2];
} xmm128_t;
static const xmm128_t expected_state[8] = {
{{0x8881828384858687ULL, 0x1811121314151617ULL}},
{{0x9992939495969798ULL, 0x2922232425262728ULL}},
{{0xAAA3A4A5A6A7A8A9ULL, 0x3A33343536373839ULL}},
{{0xBBB4B5B6B7B8B9BAULL, 0x4B4445464748494AULL}},
{{0xCCC5C6C7C8C9CACBULL, 0x5C55565758595A5BULL}},
{{0xDDD6D7D8D9DADBDCULL, 0x6D666768696A6B6CULL}},
{{0xEEE7E8E9EAEBECEDULL, 0x7E7778797A7B7C7DULL}},
{{0xFFF8F9FAFBFCFDFEULL, 0x8F88898A8B8C8D8EULL}},
};
static const xmm128_t poisoned_state[8] = {
{{0xF0F1F2F3F4F5F6F7ULL, 0x0706050403020100ULL}},
{{0xE0E1E2E3E4E5E6E7ULL, 0x1716151413121110ULL}},
{{0xD0D1D2D3D4D5D6D7ULL, 0x2726252423222120ULL}},
{{0xC0C1C2C3C4C5C6C7ULL, 0x3736353433323130ULL}},
{{0xB0B1B2B3B4B5B6B7ULL, 0x4746454443424140ULL}},
{{0xA0A1A2A3A4A5A6A7ULL, 0x5756555453525150ULL}},
{{0x9091929394959697ULL, 0x6766656463626160ULL}},
{{0x8081828384858687ULL, 0x7776757473727170ULL}},
};
static inline uint64_t read_xcr0(void)
{
uint32_t eax, edx;
__asm__ volatile("xgetbv" : "=a"(eax), "=d"(edx) : "c"(0));
return ((uint64_t)edx << 32) | eax;
}
static inline void xsave_plain(void* area, uint64_t mask)
{
uint32_t eax = (uint32_t)mask;
uint32_t edx = (uint32_t)(mask >> 32);
__asm__ volatile("xsave (%0)" : : "r"(area), "a"(eax), "d"(edx) : "memory");
}
static inline void xrstor_plain(const void* area, uint64_t mask)
{
uint32_t eax = (uint32_t)mask;
uint32_t edx = (uint32_t)(mask >> 32);
__asm__ volatile("xrstor (%0)" : : "r"(area), "a"(eax), "d"(edx) : "memory");
}
static inline void xsave64_rexw(void* area, uint64_t mask)
{
uint32_t eax = (uint32_t)mask;
uint32_t edx = (uint32_t)(mask >> 32);
__asm__ volatile(
"mov %0, %%rdi\n\t"
".byte 0x48, 0x0f, 0xae, 0x27"
:
: "r"(area), "a"(eax), "d"(edx)
: "rdi", "memory");
}
static inline void xrstor64_rexw(const void* area, uint64_t mask)
{
uint32_t eax = (uint32_t)mask;
uint32_t edx = (uint32_t)(mask >> 32);
__asm__ volatile(
"mov %0, %%rdi\n\t"
".byte 0x48, 0x0f, 0xae, 0x2f"
:
: "r"(area), "a"(eax), "d"(edx)
: "rdi", "memory");
}
static inline void load_xmm8_15(const xmm128_t* values)
{
__asm__ volatile(
"movdqu 0(%0), %%xmm8\n\t"
"movdqu 16(%0), %%xmm9\n\t"
"movdqu 32(%0), %%xmm10\n\t"
"movdqu 48(%0), %%xmm11\n\t"
"movdqu 64(%0), %%xmm12\n\t"
"movdqu 80(%0), %%xmm13\n\t"
"movdqu 96(%0), %%xmm14\n\t"
"movdqu 112(%0), %%xmm15\n\t"
:
: "r"(values)
: "memory", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15");
}
static inline void store_xmm8_15(xmm128_t* values)
{
__asm__ volatile(
"movdqu %%xmm8, 0(%0)\n\t"
"movdqu %%xmm9, 16(%0)\n\t"
"movdqu %%xmm10, 32(%0)\n\t"
"movdqu %%xmm11, 48(%0)\n\t"
"movdqu %%xmm12, 64(%0)\n\t"
"movdqu %%xmm13, 80(%0)\n\t"
"movdqu %%xmm14, 96(%0)\n\t"
"movdqu %%xmm15, 112(%0)\n\t"
:
: "r"(values)
: "memory");
}
static void dump_reg(const char* name, const xmm128_t* value)
{
printf(" %s = 0x%016" PRIx64 "%016" PRIx64 "\n", name, value->q[1], value->q[0]);
}
static int compare_xmm8_15(const char* name)
{
static const char* reg_names[8] = {
"xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
};
xmm128_t got[8];
int failed = 0;
store_xmm8_15(got);
for (int i = 0; i < 8; ++i) {
if (memcmp(&got[i], &expected_state[i], sizeof(got[i])) != 0) {
if (!failed)
printf("%s: FAIL\n", name);
failed = 1;
dump_reg(reg_names[i], &got[i]);
}
}
if (!failed)
printf("%s: OK\n", name);
return failed;
}
static int run_roundtrip(const char* name, void (*xsave_fn)(void*, uint64_t), void (*xrstor_fn)(const void*, uint64_t))
{
struct __attribute__((aligned(64))) {
unsigned char bytes[1024];
} area;
// XRSTOR validates reserved bytes in the XSAVE image, so start from a
// zeroed buffer instead of poison-filled memory.
memset(&area, 0, sizeof(area));
load_xmm8_15(expected_state);
xsave_fn(&area, 0x3);
load_xmm8_15(poisoned_state);
xrstor_fn(&area, 0x3);
return compare_xmm8_15(name);
}
int main(void)
{
int failed = 0;
failed |= run_roundtrip("XSAVE/XRSTOR restores XMM8..XMM15", xsave_plain, xrstor_plain);
failed |= run_roundtrip("XSAVE64/XRSTOR64 restores XMM8..XMM15", xsave64_rexw, xrstor64_rexw);
return failed;
}
+2
View File
@@ -0,0 +1,2 @@
XSAVE/XRSTOR restores XMM8..XMM15: OK
XSAVE64/XRSTOR64 restores XMM8..XMM15: OK