From adab3d081b124826af66ee148d6931266f59e0aa Mon Sep 17 00:00:00 2001 From: FrontMage Date: Sat, 14 Feb 2026 16:57:02 +0800 Subject: [PATCH 1/2] [BOX32] Fix ARPL (0x63) semantics In 32-bit mode, opcode 0x63 is ARPL Ew, Gw, not MOVSXD.\n\nImplement correct ARPL behavior (conditional update of RPL + ZF only) in the interpreter and ARM64 dynarec, and update opcode memory access decode for 32-bit mode. --- src/dynarec/arm64/dynarec_arm64_00.c | 41 +++++++++++++++++++++------- src/emu/x64run.c | 24 ++++++++++++---- src/libtools/decopcode.c | 8 +++++- 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/dynarec/arm64/dynarec_arm64_00.c b/src/dynarec/arm64/dynarec_arm64_00.c index 9acb193ed4..b42723075e 100644 --- a/src/dynarec/arm64/dynarec_arm64_00.c +++ b/src/dynarec/arm64/dynarec_arm64_00.c @@ -979,16 +979,37 @@ uintptr_t dynarec64_00(dynarec_arm_t* dyn, uintptr_t addr, uintptr_t ip, int nin *ok = 0; } break; - case 0x63: - if(rex.is32bits) { - // ARPL here - DEFAULT; - } else { - INST_NAME("MOVSXD Gd, Ed"); - nextop = F8; - GETGD; - if(rex.w) { - if(MODREG) { // reg <= reg + case 0x63: + if(rex.is32bits) { + // ARPL r/m16, r16 + // If dst.RPL < src.RPL then dst.RPL = src.RPL and ZF=1, else ZF=0. + // Only ZF is modified. + INST_NAME("ARPL Ew, Gw"); + nextop = F8; + SETFLAGS(X_ZF, SF_SUBSET); + SET_DFNONE(); + GETEW(x1, 0); + GETGW(x2); + // Extract RPL (low 2 bits) + UBFXw(x3, ed, 0, 2); + UBFXw(x4, gd, 0, 2); + // need_update = (dst_rpl < src_rpl) + CMPSw_REG(x3, x4); + CSETw(x5, cLT); + // ZF = need_update + BFIw(xFlags, x5, F_ZF, 1); + // If no update needed then skip the writeback. + CBZw_MARK(x5); + // Update dest selector's low 2 bits. + BFXILw(ed, gd, 0, 2); + EWBACK; + MARK; + } else { + INST_NAME("MOVSXD Gd, Ed"); + nextop = F8; + GETGD; + if(rex.w) { + if(MODREG) { // reg <= reg SXTWx(gd, TO_NAT((nextop & 7) + (rex.b << 3))); } else { // mem <= reg SMREAD(); diff --git a/src/emu/x64run.c b/src/emu/x64run.c index 98b33880d2..da8b6ae149 100644 --- a/src/emu/x64run.c +++ b/src/emu/x64run.c @@ -518,13 +518,27 @@ int Run(x64emu_t *emu, int step) break; case 0x63: /* MOVSXD Gd,Ed */ nextop = F8; - GETE4(0); - GETGD; if(rex.is32bits) { - // ARPL here - // faking to always happy... - SET_FLAG(F_ZF); + // ARPL r/m16, r16 + // If dest.RPL < src.RPL then dest.RPL = src.RPL and ZF=1, else ZF=0. + // Only ZF is modified. + CHECK_FLAGS(emu); + + GETEW(0); + GETGW; + uint16_t dst = EW->word[0]; + uint16_t src = GW->word[0]; + uint16_t dst_rpl = dst & 0x3; + uint16_t src_rpl = src & 0x3; + if(dst_rpl < src_rpl) { + EW->word[0] = (dst & 0xFFFC) | src_rpl; + SET_FLAG(F_ZF); + } else { + CLEAR_FLAG(F_ZF); + } } else { + GETE4(0); + GETGD; if(rex.w) GD->sq[0] = ED->sdword[0]; else diff --git a/src/libtools/decopcode.c b/src/libtools/decopcode.c index 53a5c1d4a3..c63e15e594 100644 --- a/src/libtools/decopcode.c +++ b/src/libtools/decopcode.c @@ -228,7 +228,6 @@ int decode_opcode(uintptr_t rip, int is32bits) case 0x39: case 0x3a: case 0x3b: - case 0x63: case 0x69: case 0x6B: case 0x84: @@ -238,6 +237,13 @@ int decode_opcode(uintptr_t rip, int is32bits) case 0x8E: nextop = addr[idx++]; return (MODREG)?0:(OPCODE_READ); + case 0x63: + // In 32-bit mode this opcode is ARPL Ew, Gw (read + conditional write to r/m16). + // In 64-bit mode this opcode is MOVSXD Gd, Ed (read-only). + nextop = addr[idx++]; + if(is32bits) + return (MODREG)?0:(OPCODE_WRITE|OPCODE_READ); + return (MODREG)?0:(OPCODE_READ); case 0x06: case 0x0E: case 0x16: From 464688216d377b4f103499e772cc6ea99e58288a Mon Sep 17 00:00:00 2001 From: FrontMage Date: Sat, 14 Feb 2026 16:57:34 +0800 Subject: [PATCH 2/2] tests32/extensions: add ARPL regression test Add an ARPL 0x63 regression test for BOX32 builds.\n\nThe test validates conditional RPL update and ZF-only flag behavior (other status flags preserved). --- tests32/extensions/arpl | Bin 0 -> 14812 bytes tests32/extensions/arpl.c | 98 ++++++++++++++++++++++++++++++++++++ tests32/extensions/arpl.txt | 1 + 3 files changed, 99 insertions(+) create mode 100755 tests32/extensions/arpl create mode 100644 tests32/extensions/arpl.c create mode 100644 tests32/extensions/arpl.txt diff --git a/tests32/extensions/arpl b/tests32/extensions/arpl new file mode 100755 index 0000000000000000000000000000000000000000..1eea2b55a777fb377e0ebc6eec560948f1b89c1f GIT binary patch literal 14812 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 B!$$xB literal 0 HcmV?d00001 diff --git a/tests32/extensions/arpl.c b/tests32/extensions/arpl.c new file mode 100644 index 0000000000..09cad00494 --- /dev/null +++ b/tests32/extensions/arpl.c @@ -0,0 +1,98 @@ +#include +#include +#include + +static inline uint32_t read_eflags(void) { + uint32_t f; + __asm__ volatile( + "pushfl\n\t" + "popl %0\n\t" + : "=r"(f) + : + : "memory"); + return f; +} + +static inline void write_eflags(uint32_t f) { + __asm__ volatile( + "pushl %0\n\t" + "popfl\n\t" + : + : "r"(f) + : "cc", "memory"); +} + +static inline uint32_t do_arpl(uint32_t *eax_inout, uint32_t ebx, uint32_t eflags_in) { + uint32_t flags_out; + uint32_t eax = *eax_inout; + + __asm__ volatile( + "pushl %[fin]\n\t" + "popfl\n\t" + "arpl %%bx, %%ax\n\t" + "pushfl\n\t" + "popl %[fout]\n\t" + : [fout] "=r"(flags_out), "+a"(eax) + : "b"(ebx), [fin] "r"(eflags_in) + : "cc", "memory"); + + *eax_inout = eax; + return flags_out; +} + +int main(void) { + // Only check the status flags ARPL is documented to preserve (all but ZF). + // We use a mask of classic status flags: CF, PF, AF, ZF, SF, OF. + const uint32_t kStatusMask = (1u << 0) | (1u << 2) | (1u << 4) | (1u << 6) | (1u << 7) | (1u << 11); + const uint32_t kZF = (1u << 6); + + // Start from current flags so we don't fight reserved / privileged bits. + uint32_t base = read_eflags(); + // Baseline: CF=1, PF=1, AF=0, ZF=0, SF=1, OF=1. + uint32_t wanted = (base & ~kStatusMask) | (1u << 0) | (1u << 2) | (1u << 7) | (1u << 11); + wanted &= ~kZF; + + for (uint32_t dst_rpl = 0; dst_rpl < 4; ++dst_rpl) { + for (uint32_t src_rpl = 0; src_rpl < 4; ++src_rpl) { + uint32_t eax = 0xFFFF0000u | (0x1FCu + dst_rpl); // low 16 bits end with RPL + uint32_t ebx = 0xEEEE0000u | (0x201u + src_rpl); // low 16 bits end with RPL + + // Reset flags to known state before each ARPL. + write_eflags(wanted); + + uint32_t flags = do_arpl(&eax, ebx, wanted); + + uint16_t dst_before = (uint16_t)(0x1FCu + dst_rpl); + uint16_t src = (uint16_t)(0x201u + src_rpl); + + uint16_t exp_dst = dst_before; + uint32_t need_update = ((dst_before & 0x3) < (src & 0x3)); + if (need_update) { + exp_dst = (uint16_t)((dst_before & 0xFFFCu) | (src & 0x3u)); + } + + // Check destination low 16 and upper 16 untouched. + if (((uint16_t)eax) != exp_dst) { + printf("FAIL: dst mismatch dst_rpl=%u src_rpl=%u got=0x%04x exp=0x%04x\n", + dst_rpl, src_rpl, (unsigned)((uint16_t)eax), (unsigned)exp_dst); + return 1; + } + if ((eax & 0xFFFF0000u) != 0xFFFF0000u) { + printf("FAIL: upper eax modified dst_rpl=%u src_rpl=%u got=0x%08x\n", dst_rpl, src_rpl, eax); + return 1; + } + + // Check ZF and that other status flags were preserved. + uint32_t exp_flags = (wanted & ~kZF) | (need_update ? kZF : 0); + if (((flags ^ exp_flags) & kStatusMask) != 0) { + printf("FAIL: flags mismatch dst_rpl=%u src_rpl=%u got=0x%08x exp=0x%08x\n", + dst_rpl, src_rpl, flags, exp_flags); + return 1; + } + } + } + + printf("arpl: ok\n"); + return 0; +} + diff --git a/tests32/extensions/arpl.txt b/tests32/extensions/arpl.txt new file mode 100644 index 0000000000..648e02da38 --- /dev/null +++ b/tests32/extensions/arpl.txt @@ -0,0 +1 @@ +arpl: ok