diff --git a/linux-user/i386/signal.c b/linux-user/i386/signal.c index 857a126699..4b5b8992bb 100644 --- a/linux-user/i386/signal.c +++ b/linux-user/i386/signal.c @@ -454,6 +454,7 @@ static void setup_sigcontext(struct target_sigcontext *sc, CPUState *cs = env_cpu(env); #ifdef CONFIG_LATX save_xmm_to_env(env); + cpu_x86_canonicalize_latx_mmx_state(env); #endif #ifndef TARGET_X86_64 uint16_t magic; @@ -638,6 +639,8 @@ void setup_frame(int sig, struct target_sigaction *ka, unlock_user_struct(frame, frame_addr, 1); + cpu_x86_init_user_x87(env); + return; give_sigsegv: @@ -728,6 +731,8 @@ void setup_rt_frame(int sig, struct target_sigaction *ka, unlock_user_struct(frame, frame_addr, 1); + cpu_x86_init_user_x87(env); + return; give_sigsegv: @@ -836,6 +841,10 @@ restore_sigcontext(CPUX86State *env, struct target_sigcontext *sc) #endif unlock_user_struct(fpstate, fpstate_addr, 0); #ifdef CONFIG_LATX + if (!err) { + cpu_x86_sync_latx_fcsr(env); + cpu_x86_sync_latx_fpu_mode(env); + } load_xmm_from_env(env); #endif } else { diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 7a3f8aeb89..26aa311152 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -1211,6 +1211,15 @@ typedef union { MMXReg mmx; } FPReg; +#ifdef CONFIG_LATX +enum { + LATX_FPU_MODE_MMX, + LATX_FPU_MODE_X87, + /* Reload the MMX view on reentry; the restored env stays authoritative. */ + LATX_FPU_MODE_RESTORED, +}; +#endif + typedef struct { uint64_t base; uint64_t mask; @@ -1424,7 +1433,7 @@ typedef struct CPUX86State { int func_index; int last_func_index; #endif - bool mode_fpu; + uint8_t mode_fpu; bool fpu_clobber; #endif /* standard registers */ @@ -1934,6 +1943,12 @@ void cpu_x86_fxsave(CPUX86State *s, target_ulong ptr); void cpu_x86_fxrstor(CPUX86State *s, target_ulong ptr); void cpu_x86_xsave(CPUX86State *s, target_ulong ptr); void cpu_x86_xrstor(CPUX86State *s, target_ulong ptr); +void cpu_x86_init_user_x87(CPUX86State *s); +#ifdef CONFIG_LATX +void cpu_x86_canonicalize_latx_mmx_state(CPUX86State *s); +void cpu_x86_sync_latx_fcsr(CPUX86State *s); +void cpu_x86_sync_latx_fpu_mode(CPUX86State *s); +#endif /* you can call this signal handler from your SIGBUS and SIGSEGV signal handlers to inform the virtual CPU of exceptions. non zero diff --git a/target/i386/latx/translator/translate.c b/target/i386/latx/translator/translate.c index aa70c483e9..5c70cacd11 100644 --- a/target/i386/latx/translator/translate.c +++ b/target/i386/latx/translator/translate.c @@ -4068,8 +4068,14 @@ void tr_load_registers_from_env(uint8 gpr_to_load, uint8 fpr_to_load, /* check current mode(mmx/fpu) */ if (option_softfpu == 2) { - la_ld_wu(mode_fpu, env_ir2_opnd, lsenv_offset_of_mode_fpu(lsenv)); - la_bne(mode_fpu, zero_ir2_opnd, label_fpu); + la_ld_bu(mode_fpu, env_ir2_opnd, lsenv_offset_of_mode_fpu(lsenv)); + if (fpr_to_load == 0xff) { + /* A signal restore must initialize MMX without selecting it. */ + la_xori(mode_fpu, mode_fpu, LATX_FPU_MODE_X87); + la_beq(mode_fpu, zero_ir2_opnd, label_fpu); + } else { + la_bne(mode_fpu, zero_ir2_opnd, label_fpu); + } } for (i = 0; i < 8; i++) { @@ -4320,7 +4326,9 @@ static inline void helper_restore_reg(IR2_OPND opnd) void gen_test_page_flag(IR2_OPND mem_opnd, int mem_imm, uint32_t flag, unsigned int mem_size) { +#if TARGET_ABI_BITS == 32 uint32_t required_flag = flag & PAGE_WRITE ? PAGE_WRITE : PAGE_READ; +#endif if (!option_mem_test) { #if TARGET_ABI_BITS == 32 @@ -4351,7 +4359,9 @@ void gen_test_page_flag(IR2_OPND mem_opnd, int mem_imm, uint32_t flag, IR2_OPND label1 = ra_alloc_label(); IR2_OPND label2 = ra_alloc_label(); IR2_OPND label_fault = ra_alloc_label(); +#if TARGET_ABI_BITS == 32 IR2_OPND label_check_end = ra_alloc_label(); +#endif bool need_restore0 = false; bool need_restore1 = false; bool need_restore2 = false; diff --git a/target/i386/tcg/fpu_helper.c b/target/i386/tcg/fpu_helper.c index 1efc9d70b7..b731219003 100644 --- a/target/i386/tcg/fpu_helper.c +++ b/target/i386/tcg/fpu_helper.c @@ -3222,6 +3222,95 @@ void cpu_x86_xrstor(CPUX86State *env, target_ulong ptr) { do_xrstor(env, ptr, -1, 0); } + +#ifdef CONFIG_LATX +void cpu_x86_canonicalize_latx_mmx_state(CPUX86State *env) +{ + int i; + + if (env->mode_fpu) { + return; + } + + env->fpstt = 0; + memset(env->fptags, 0, sizeof(env->fptags)); + for (i = 0; i < 8; i++) { + CPU_LDoubleU reg = { .d = env->fpregs[i].d }; + + reg.l.upper = 0xffff; + env->fpregs[i].d = reg.d; + } +} + +static bool latx_x87_state_is_mmx(const CPUX86State *env) +{ + int i; + + /* MMX sets every x87 tag valid and every physical exponent to 0xffff. */ + for (i = 0; i < 8; i++) { + CPU_LDoubleU reg = { .d = env->fpregs[i].d }; + + if (env->fptags[i] || reg.l.upper != 0xffff) { + return false; + } + } + return true; +} + +void cpu_x86_sync_latx_fcsr(CPUX86State *env) +{ + static const uint8_t rounding_map[4] = { 0, 3, 2, 1 }; + static const uint8_t exception_map[5] = { 5, 4, 3, 2, 0 }; + uint32_t fpuc = env->fpuc; + uint32_t fpus = env->fpus; + uint32_t rc = (fpuc & FPU_RC_MASK) >> FPU_RC_SHIFT; + uint32_t fcsr = rounding_map[rc] << 8; + int i; + + /* Keep these exception mappings in sync with tr-fctrl.c. */ + for (i = 0; i < 5; i++) { + uint32_t x87_mask = 1 << exception_map[i]; + + if ((!option_enable_fcsr_exc || i != 0) && !(fpuc & x87_mask)) { + fcsr |= 1 << i; + } + if (fpus & x87_mask) { + fcsr |= 1 << (16 + i); + } + } + + env->fcsr = fcsr; +} + +void cpu_x86_sync_latx_fpu_mode(CPUX86State *env) +{ + /* + * MMX and a full x87 stack of negative NaNs/infinities have identical + * tag/exponent encodings. In softfpu=2 the architectural state lives + * in env, whereas MMX uses host registers. Load the latter on reentry + * without allowing their stale contents to overwrite subsequent x87 + * writes. Keep this state until MMX/EMMS explicitly selects a mode: + * translation/dispatch may clobber host MMX before the first guest use. + * Softfpu=1 already reloads MMX on every reentry, but must also avoid + * canonicalizing a negative-NaN x87 stack on a subsequent signal. + */ + if (option_softfpu) { + env->mode_fpu = LATX_FPU_MODE_RESTORED; + } else { + env->mode_fpu = !latx_x87_state_is_mmx(env); + } +} +#endif + +void cpu_x86_init_user_x87(CPUX86State *env) +{ + do_fninit(env); + +#ifdef CONFIG_LATX + env->fcsr = 0; + env->mode_fpu = 1; +#endif +} #endif uint64_t helper_xgetbv(CPUX86State *env, uint32_t ecx) diff --git a/tests/README.md b/tests/README.md index 568cecf6a9..9fb43b4f4a 100644 --- a/tests/README.md +++ b/tests/README.md @@ -104,6 +104,33 @@ and then run tests with the bundled `meson/meson.py`, or vice versa. To run the integration suite, replace `lat-pr-fast` with `latx-integration`. +The `test-x87-signal-mode` integration test builds five static x86-64 guests +with Clang/LLD only when explicitly run. On a target without that compiler, +build the same source on an x86-64 Linux host: + +```sh +mkdir -p x87-guests +for case_id in 0 1 2 3 4; do + gcc -nostdlib -static -no-pie -DCASE=$case_id \ + tests/integration/x87-signal-mode.S \ + -o x87-guests/x87-signal-mode-$case_id +done +``` + +Copy `x87-guests` to the LoongArch target, then run: + +```sh +LATX_X87_SIGNAL_GUEST_DIR=/absolute/path/to/x87-guests \ + meson test -C build64-tests --suite latx-integration \ + test-x87-signal-mode --print-errorlogs +``` + +The cases cover full x87 stacks of negative NaNs and infinities, a second +signal after an x87 write, MMX restoration, and x87 handler initialization +and rounding restoration. Each runs in hard-float and softfpu=1/2, with +both TB and TU translation. Compile the fixtures from the same checkout +being tested; a supplied directory with a missing executable is a failure. + Before submitting a new test target, verify both of these: 1. A normal product build without `--enable-tests` does not build the test. diff --git a/tests/integration/registrations/process/meson.build b/tests/integration/registrations/process/meson.build index eddba40a6b..f1fd68afbd 100644 --- a/tests/integration/registrations/process/meson.build +++ b/tests/integration/registrations/process/meson.build @@ -1,4 +1,13 @@ if 'x86_64-linux-user' in target_dirs + latx_integration_tests += [{ + 'name': 'test-x87-signal-mode', + 'runner': find_program('../../test-x87-signal-mode.sh'), + 'args': [ + emulators['latx-x86_64'], + files('../../x87-signal-mode.S'), + ], + 'timeout': 120, + }] latx_integration_tests += [{ 'name': 'test-proc-readdir', 'runner': find_program('../../test-proc-readdir.sh'), diff --git a/tests/integration/test-x87-signal-mode.sh b/tests/integration/test-x87-signal-mode.sh new file mode 100755 index 0000000000..d3be0ce6ce --- /dev/null +++ b/tests/integration/test-x87-signal-mode.sh @@ -0,0 +1,47 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +set -eu + +emulator=$(readlink -f "$1") +source_file=$(readlink -f "$2") +workdir=$(mktemp -d) +trap 'rm -rf "$workdir"' EXIT HUP INT TERM + +guest_dir=${LATX_X87_SIGNAL_GUEST_DIR:-$workdir} +if [ -n "${LATX_X87_SIGNAL_GUEST_DIR:-}" ]; then + : # Allow guests built from this source on an x86 build host. +elif command -v clang-19 >/dev/null 2>&1; then + clang=clang-19 +elif command -v clang >/dev/null 2>&1; then + clang=clang +else + echo "SKIP: clang is required to build the x86_64 guest" + exit 77 +fi + +for case_id in 0 1 2 3 4; do + guest="$guest_dir/x87-signal-mode-$case_id" + if [ -z "${LATX_X87_SIGNAL_GUEST_DIR:-}" ]; then + "$clang" --target=x86_64-linux-gnu -fuse-ld=lld -nostdlib -static \ + -Wl,--build-id=none -DCASE=$case_id "$source_file" -o "$guest" + fi + if [ ! -x "$guest" ]; then + echo "FAIL: guest executable missing: $guest" >&2 + exit 1 + fi + for softfpu in 0 1 2; do + for tu in 0 1; do + if LATX_AOT=0 LATX_MT=0 LATX_TU=$tu LATX_SOFTFPU=$softfpu \ + timeout -s KILL 10 "$emulator" "$guest" \ + > "$workdir/result"; then + echo "PASS: x87 signal case=$case_id softfpu=$softfpu tu=$tu" + else + status=$? + printf 'FAIL: case=%s softfpu=%s tu=%s exit=%s\n' \ + "$case_id" "$softfpu" "$tu" "$status" >&2 + od -An -tx1 "$workdir/result" >&2 + exit "$status" + fi + done + done +done diff --git a/tests/integration/x87-signal-mode.S b/tests/integration/x87-signal-mode.S new file mode 100644 index 0000000000..f3f25c07ac --- /dev/null +++ b/tests/integration/x87-signal-mode.S @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * Static x86-64 Linux guest. + * Exit 0: fld1 survives getpid after SIGUSR1. Exit 22: value corrupted. + * stdout is two raw 80-bit values: before and after the getpid syscall. + * CASE: 0 = negative NaNs, 1 = negative infinities, 2 = second signal, + * 3 = MMX restore, 4 = x87 handler entry and rounding restore. + */ +#ifndef CASE +#define CASE 0 +#endif +.section .data +.balign 8 +action: + .quad handler, 0x04000000, restorer, 0 +negative_nan: +#if CASE == 1 + .quad 0x8000000000000000 +#else + .quad 0xc123450000000000 +#endif + .word 0xffff +mmx_value: + .quad 0x0123456789abcdef +round_down: .word 0x077f +fraction: .quad 0x3ffc000000000000 /* double 1.75 */ +.section .bss +.balign 16 +observed: .skip 20 +seen: .skip 4 +handler_cw: .skip 2 +handler_sw: .skip 2 +integer: .skip 4 +.section .text +.global _start +_start: + mov $13, %eax + mov $10, %edi + lea action(%rip), %rsi + xor %edx, %edx + mov $8, %r10d + syscall + test %rax, %rax + js setup_failed + mov $39, %eax + syscall + mov %eax, %r12d + fninit +#if CASE == 3 + movq mmx_value(%rip), %mm0 + movq %mm0, %mm1 + movq %mm0, %mm2 + movq %mm0, %mm3 + movq %mm0, %mm4 + movq %mm0, %mm5 + movq %mm0, %mm6 + movq %mm0, %mm7 +#elif CASE == 4 + fldcw round_down(%rip) + fld1 +#else + .rept 8 + fldt negative_nan(%rip) + .endr +#endif + mov $62, %eax + mov %r12d, %edi + mov $10, %esi + syscall + cmpl $1, seen(%rip) + jne setup_failed +#if CASE == 3 + mov mmx_value(%rip), %rdx + .irp n,0,1,2,3,4,5,6,7 + movq %mm\n, %rax + cmp %rdx, %rax + jne corrupted + .endr + emms + xor %edi, %edi + jmp finish +#elif CASE == 4 + cmpw $0x037f, handler_cw(%rip) + jne precheck_failed + cmpw $0, handler_sw(%rip) + jne precheck_failed + fstpt observed(%rip) + movabs $0x8000000000000000, %rax + cmp %rax, observed(%rip) + jne corrupted + cmpw $0x3fff, observed+8(%rip) + jne corrupted + fldl fraction(%rip) + fistpl integer(%rip) + cmpl $1, integer(%rip) + jne corrupted + xor %edi, %edi + jmp finish +#else + fstp %st(0) + fld1 + fstpt observed(%rip) + fldt observed(%rip) +#if CASE == 2 + mov $62, %eax + mov %r12d, %edi + mov $10, %esi + syscall + cmpl $2, seen(%rip) + jne setup_failed +#endif + mov $39, %eax + syscall + fstpt observed+10(%rip) + mov $1, %eax + mov $1, %edi + lea observed(%rip), %rsi + mov $20, %edx + syscall + movabs $0x8000000000000000, %rax + cmp %rax, observed(%rip) + jne precheck_failed + cmpw $0x3fff, observed+8(%rip) + jne precheck_failed + cmp %rax, observed+10(%rip) + jne corrupted + cmpw $0x3fff, observed+18(%rip) + jne corrupted + xor %edi, %edi + jmp finish +#endif +setup_failed: + mov $20, %edi + jmp finish +precheck_failed: + mov $21, %edi + jmp finish +corrupted: + mov $22, %edi +finish: + mov $60, %eax + syscall +handler: +#if CASE == 3 + .irp n,0,1,2,3,4,5,6,7 + pxor %mm\n, %mm\n + .endr + emms + fninit +#elif CASE == 4 + fnstcw handler_cw(%rip) + fnstsw handler_sw(%rip) + fninit +#endif + incl seen(%rip) + ret +restorer: + mov $15, %eax + syscall +.section .note.GNU-stack,"",@progbits