From d2511f9a3f1846defb97bc94ff7e5d0462b15810 Mon Sep 17 00:00:00 2001 From: Leslie Zhai Date: Tue, 27 Oct 2026 10:39:33 +0800 Subject: [PATCH] LATX, fix: Translate 64-bit XSAVE and XRSTOR variants Translate XSAVE64, XSAVEOPT64, and XRSTOR64 with the existing XSAVE/XRSTOR wrappers, matching the FXSAVE64/FXRSTOR64 handling. Wine's x86-64 syscall dispatcher uses xsave64 to preserve the non-volatile XMM6-XMM15 registers across win32u calls. The missing translation left those registers zeroed, so notepad's About and Find dialogs were created with zero size and appeared as invisible 1x1 modal windows. Add an x86-64 integration regression that round-trips XMM6/XMM7 through xsave64/xrstor64 in JIT, cold AOT, and hot AOT paths. Update the SHBR inventory, implicit-semantics model, and audit expectations for the new 64-bit XSAVE variants so translator coverage stays consistent. Classify the 64-bit XSAVE variants as required softfpu regions so LATX_SOFTFPU=2 saves and restores XMM and high GPR state around the helpers. Fixes Wine notepad Help->About and Ctrl+F on LoongArch hosts. Signed-off-by: Leslie Zhai --- scripts/test_latx_hbr_audit.py | 24 ++++-- scripts/test_latx_hbr_metrics.py | 4 +- .../latx/optimization/hbr-xmm-semantics.inc | 3 + .../latx/optimization/hbr-xmm-semantics.json | 39 ++++++++-- target/i386/latx/optimization/hbr.c | 6 ++ target/i386/latx/translator/translate.c | 6 ++ .../integration/registrations/avx/meson.build | 6 ++ tests/integration/xsave64-roundtrip.S | 76 +++++++++++++++++++ 8 files changed, 147 insertions(+), 17 deletions(-) create mode 100644 tests/integration/xsave64-roundtrip.S diff --git a/scripts/test_latx_hbr_audit.py b/scripts/test_latx_hbr_audit.py index e7dc25a907..f5b747dbe3 100644 --- a/scripts/test_latx_hbr_audit.py +++ b/scripts/test_latx_hbr_audit.py @@ -20,13 +20,13 @@ class HbrAuditTest(unittest.TestCase): def test_repository_inventory(self): result = latx_hbr_audit.audit(REPO_ROOT) - self.assertEqual(result["counts"]["translator_supported"], 1098) - self.assertEqual(result["counts"]["shbr_relevant"], 774) - self.assertEqual(result["counts"]["opcode_specific"], 774) + self.assertEqual(result["counts"]["translator_supported"], 1101) + self.assertEqual(result["counts"]["shbr_relevant"], 777) + self.assertEqual(result["counts"]["opcode_specific"], 777) self.assertEqual(result["counts"]["conservative_unmodeled"], 0) - self.assertEqual(len(result["rows"]), 774) - self.assertEqual(len(result["promoted_opcode_specific"]), 618) - self.assertEqual(result["generated_opcode_specific"], 519) + self.assertEqual(len(result["rows"]), 777) + self.assertEqual(len(result["promoted_opcode_specific"]), 621) + self.assertEqual(result["generated_opcode_specific"], 522) gather = next( row for row in result["rows"] if row["mnemonic"] == "VGATHERDPD" ) @@ -36,13 +36,21 @@ def test_repository_inventory(self): ) self.assertEqual( result["translator_mnemonic_sha256"], - "32bdf9f0e48c0b73ac1f3f0e914d2af621540ffd7c6ccebb86d059c14137250d", + "f8b9761a04c987ad075b4986d1dec5aab06a2f9c6dd992c3adcba6881a25a2a8", ) xrstor = next( row for row in result["rows"] if row["mnemonic"] == "XRSTOR" ) self.assertEqual(xrstor["policy"], "implicit-read-write-all") self.assertIn("preserve path", xrstor["high32"]["reads"]) + xsave64 = next( + row for row in result["rows"] if row["mnemonic"] == "XSAVE64" + ) + self.assertEqual(xsave64["policy"], "implicit-read-all") + xrstor64 = next( + row for row in result["rows"] if row["mnemonic"] == "XRSTOR64" + ) + self.assertEqual(xrstor64["policy"], "implicit-read-write-all") def test_commented_hbr_case_is_ignored(self): models = latx_hbr_audit.parse_hbr_models( @@ -115,7 +123,7 @@ def test_single_domain_model_fails(self): def test_exact_family_rules(self): data = json.loads(INVENTORY.read_text(encoding="utf-8")) rules = generate_latx_hbr_semantics.exact_rules(data) - self.assertEqual(len(rules), 519) + self.assertEqual(len(rules), 522) self.assertEqual( rules["VFMADD132SS"], {"high32": "DEST", "high64": "DEST"}, diff --git a/scripts/test_latx_hbr_metrics.py b/scripts/test_latx_hbr_metrics.py index 77f164e18c..c64e517741 100644 --- a/scripts/test_latx_hbr_metrics.py +++ b/scripts/test_latx_hbr_metrics.py @@ -14,9 +14,9 @@ class ShbrMetricsTests(unittest.TestCase): def test_current_metrics(self): result = latx_hbr_metrics.collect(REPO_ROOT) - self.assertEqual(result["coverage"]["precise"], 774) + self.assertEqual(result["coverage"]["precise"], 777) self.assertEqual(result["coverage"]["conservative"], 0) - self.assertEqual(result["coverage"]["relevant"], 774) + self.assertEqual(result["coverage"]["relevant"], 777) self.assertEqual(result["generation_site_counts"]["SHBR_ON_32"], 12) self.assertEqual(result["generation_site_counts"]["SHBR_ON_64"], 10) self.assertEqual(result["minimum_removed_per_full_hit"]["lasx"], 22) diff --git a/target/i386/latx/optimization/hbr-xmm-semantics.inc b/target/i386/latx/optimization/hbr-xmm-semantics.inc index be7ca9f8cc..5c5063dd43 100644 --- a/target/i386/latx/optimization/hbr-xmm-semantics.inc +++ b/target/i386/latx/optimization/hbr-xmm-semantics.inc @@ -517,5 +517,8 @@ [WRAP(VUNPCKLPS)] = { SHBR_RULE_ACCESS, SHBR_RULE_OTHER }, [WRAP(VZEROALL)] = { SHBR_RULE_IMPLICIT, SHBR_RULE_IMPLICIT }, [WRAP(XRSTOR)] = { SHBR_RULE_IMPLICIT, SHBR_RULE_IMPLICIT }, + [WRAP(XRSTOR64)] = { SHBR_RULE_IMPLICIT, SHBR_RULE_IMPLICIT }, [WRAP(XSAVE)] = { SHBR_RULE_IMPLICIT, SHBR_RULE_IMPLICIT }, + [WRAP(XSAVE64)] = { SHBR_RULE_IMPLICIT, SHBR_RULE_IMPLICIT }, [WRAP(XSAVEOPT)] = { SHBR_RULE_IMPLICIT, SHBR_RULE_IMPLICIT }, + [WRAP(XSAVEOPT64)] = { SHBR_RULE_IMPLICIT, SHBR_RULE_IMPLICIT }, diff --git a/target/i386/latx/optimization/hbr-xmm-semantics.json b/target/i386/latx/optimization/hbr-xmm-semantics.json index fdedaf7deb..b44d5f60bf 100644 --- a/target/i386/latx/optimization/hbr-xmm-semantics.json +++ b/target/i386/latx/optimization/hbr-xmm-semantics.json @@ -18,12 +18,12 @@ "note": "Intel SDM is authoritative; Intel XED validates decoded operand access and Felix Cloutier is a per-family navigation aid." }, "expected_counts": { - "translator_supported": 1098, - "opcode_specific": 774, + "translator_supported": 1101, + "opcode_specific": 777, "conservative_unmodeled": 0, - "strict_xmm_scope": 766, + "strict_xmm_scope": 769, "ymm_alias_only": 8, - "shbr_relevant": 774 + "shbr_relevant": 777 }, "promoted_opcode_specific": [ "VADDPD", @@ -126,7 +126,7 @@ "VPSUBW", "VPXOR" ], - "translator_mnemonic_sha256": "32bdf9f0e48c0b73ac1f3f0e914d2af621540ffd7c6ccebb86d059c14137250d", + "translator_mnemonic_sha256": "f8b9761a04c987ad075b4986d1dec5aab06a2f9c6dd992c3adcba6881a25a2a8", "form_resolution": { "key": "mnemonic plus encoding family", "runtime_fields": [ @@ -244,8 +244,11 @@ "SQRTPD": {"high32": "ACCESS_READ", "high64": "ACCESS_READ"}, "SQRTPS": {"high32": "ACCESS_READ", "high64": "ACCESS_READ"}, "XRSTOR": {"high32": "IMPLICIT", "high64": "IMPLICIT"}, + "XRSTOR64": {"high32": "IMPLICIT", "high64": "IMPLICIT"}, "XSAVE": {"high32": "IMPLICIT", "high64": "IMPLICIT"}, + "XSAVE64": {"high32": "IMPLICIT", "high64": "IMPLICIT"}, "XSAVEOPT": {"high32": "IMPLICIT", "high64": "IMPLICIT"}, + "XSAVEOPT64": {"high32": "IMPLICIT", "high64": "IMPLICIT"}, "VBROADCASTF128": {"high32": "BROADCAST", "high64": "BROADCAST"}, "VBROADCASTI128": {"high32": "BROADCAST", "high64": "BROADCAST"}, "VBROADCASTSD": {"high32": "BROADCAST", "high64": "BROADCAST"}, @@ -505,12 +508,24 @@ "all-xmm" ] }, + "XSAVE64": { + "policy": "implicit-read-all", + "implicit": [ + "all-xmm" + ] + }, "XSAVEOPT": { "policy": "implicit-read-all", "implicit": [ "all-xmm" ] }, + "XSAVEOPT64": { + "policy": "implicit-read-all", + "implicit": [ + "all-xmm" + ] + }, "FXRSTOR": { "policy": "implicit-write-all", "implicit": [ @@ -530,6 +545,13 @@ ], "condition": "SSE state is restored only when EDX:EAX requests XSTATE_SSE" }, + "XRSTOR64": { + "policy": "implicit-read-write-all", + "implicit": [ + "all-xmm" + ], + "condition": "SSE state is restored only when EDX:EAX requests XSTATE_SSE" + }, "VZEROALL": { "policy": "implicit-write-all", "implicit": [ @@ -572,7 +594,7 @@ "unmodeled_groups": [ { "name": "legacy_unmodeled", - "expected_count": 141, + "expected_count": 144, "form_scope": "legacy-or-non-vex", "policy": "conservative-explicit", "mnemonics": [ @@ -715,8 +737,11 @@ "SQRTPD", "SQRTPS", "XRSTOR", + "XRSTOR64", "XSAVE", - "XSAVEOPT" + "XSAVE64", + "XSAVEOPT", + "XSAVEOPT64" ] }, { diff --git a/target/i386/latx/optimization/hbr.c b/target/i386/latx/optimization/hbr.c index 1f6c618f84..e1a8380e91 100644 --- a/target/i386/latx/optimization/hbr.c +++ b/target/i386/latx/optimization/hbr.c @@ -1884,7 +1884,9 @@ static bool apply_implicit_semantic(TranslationBlock *tb, IR1_INST *ir1, case WRAP(FXSAVE): case WRAP(FXSAVE64): case WRAP(XSAVE): + case WRAP(XSAVE64): case WRAP(XSAVEOPT): + case WRAP(XSAVEOPT64): ir1->shbr_read |= SHBR_XMM_ALL; for (int i = 0; i < XMM_NUM; i++) { ir1->xmm_use |= xmm[i]; @@ -1898,6 +1900,7 @@ static bool apply_implicit_semantic(TranslationBlock *tb, IR1_INST *ir1, } return true; case WRAP(XRSTOR): + case WRAP(XRSTOR64): /* XRSTOR preserves XMM when EDX:EAX does not request SSE state. */ ir1->shbr_read |= SHBR_XMM_ALL; ir1->shbr_def |= SHBR_XMM_ALL; @@ -1932,10 +1935,13 @@ static bool has_implicit_semantic(IR1_INST *ir1) case WRAP(FXSAVE): case WRAP(FXSAVE64): case WRAP(XSAVE): + case WRAP(XSAVE64): case WRAP(XSAVEOPT): + case WRAP(XSAVEOPT64): case WRAP(FXRSTOR): case WRAP(FXRSTOR64): case WRAP(XRSTOR): + case WRAP(XRSTOR64): case WRAP(VZEROALL): case WRAP(BLENDVPD): case WRAP(BLENDVPS): diff --git a/target/i386/latx/translator/translate.c b/target/i386/latx/translator/translate.c index 83d1c3a625..7d6b364694 100644 --- a/target/i386/latx/translator/translate.c +++ b/target/i386/latx/translator/translate.c @@ -1771,8 +1771,11 @@ static bool (*translate_functions[])(IR1_INST *) = { TRANS_FUNC_GEN(XGETBV, xgetbv_wrap), TRANS_FUNC_GEN(XSETBV, xsetbv_wrap), TRANS_FUNC_GEN(XSAVE, xsave_wrap), + TRANS_FUNC_GEN(XSAVE64, xsave_wrap), TRANS_FUNC_GEN(XSAVEOPT, xsaveopt_wrap), + TRANS_FUNC_GEN(XSAVEOPT64, xsaveopt_wrap), TRANS_FUNC_GEN(XRSTOR, xrstor_wrap), + TRANS_FUNC_GEN(XRSTOR64, xrstor_wrap), TRANS_FUNC_GEN(VPBROADCASTD, vpbroadcastd), //TRANS_FUNC_GEN(VPMAXSD, vpmaxsd), //TRANS_FUNC_GEN(VPMINSD, vpminsd), @@ -2414,8 +2417,11 @@ static SoftFPURegionClass softfpu_region_class(IR1_OPCODE opcode) case dt_X86_INS_XGETBV: case dt_X86_INS_XSETBV: case dt_X86_INS_XSAVE: + case dt_X86_INS_XSAVE64: case dt_X86_INS_XSAVEOPT: + case dt_X86_INS_XSAVEOPT64: case dt_X86_INS_XRSTOR: + case dt_X86_INS_XRSTOR64: #endif return SOFTFPU_REGION_REQUIRED; case dt_X86_INS_FILD: diff --git a/tests/integration/registrations/avx/meson.build b/tests/integration/registrations/avx/meson.build index b4c9589dba..2f889ca69d 100644 --- a/tests/integration/registrations/avx/meson.build +++ b/tests/integration/registrations/avx/meson.build @@ -12,6 +12,12 @@ if 'x86_64-linux-user' in target_dirs 'args': [emulators['latx-x86_64'], files('../../sse41-ymmh-preserve.asm')], 'timeout': 120, }] + latx_integration_tests += [{ + 'name': 'test-xsave64-roundtrip', + 'runner': find_program('../../test-avx-regressions.py'), + 'args': [emulators['latx-x86_64'], files('../../xsave64-roundtrip.S')], + 'timeout': 120, + }] latx_integration_tests += [{ 'name': 'test-avx-compare-jcc', 'runner': find_program('../../test-avx-regressions.py'), diff --git a/tests/integration/xsave64-roundtrip.S b/tests/integration/xsave64-roundtrip.S new file mode 100644 index 0000000000..2331368f64 --- /dev/null +++ b/tests/integration/xsave64-roundtrip.S @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * x86-64 XSAVE64/XRSTOR64 must save and restore the non-volatile XMM + * registers. Wine's syscall dispatcher relies on this across win32u calls; + * a missing XSAVE64 translation corrupts dialog sizes and other state held + * in XMM6-XMM15. + */ +.intel_syntax noprefix +.global _start +.section .text +_start: + lea rbx, [area] + add rbx, 63 + and rbx, -64 + + movdqu xmm6, [pat6] + movdqu xmm7, [pat7] + + mov eax, 3 + xor edx, edx + xsave64 [rbx] + + mov rax, [rbx + 0x100] + cmp rax, [pat6] + jne fail + mov rax, [rbx + 0x108] + cmp rax, [pat6 + 8] + jne fail + mov rax, [rbx + 0x110] + cmp rax, [pat7] + jne fail + mov rax, [rbx + 0x118] + cmp rax, [pat7 + 8] + jne fail + + movdqu xmm6, [pat8] + movdqu xmm7, [pat9] + + mov eax, 3 + xor edx, edx + xrstor64 [rbx] + + movdqu [actual], xmm6 + movdqu [actual + 16], xmm7 + mov rax, [actual] + cmp rax, [pat6] + jne fail + mov rax, [actual + 8] + cmp rax, [pat6 + 8] + jne fail + mov rax, [actual + 16] + cmp rax, [pat7] + jne fail + mov rax, [actual + 24] + cmp rax, [pat7 + 8] + jne fail + + xor edi, edi + jmp exit +fail: + mov edi, 1 +exit: + mov eax, 60 + syscall + +.section .rodata +.p2align 4 +pat6: .quad 0x1111111111111111, 0x2222222222222222 +pat7: .quad 0x3333333333333333, 0x4444444444444444 +pat8: .quad 0x5555555555555555, 0x6666666666666666 +pat9: .quad 0x7777777777777777, 0x8888888888888888 + +.section .bss +.p2align 6 +area: .skip 2048 +actual: .skip 32