From 2589376e6e12084a3f0d743498225c18a8efeb71 Mon Sep 17 00:00:00 2001 From: Zewei Yang Date: Fri, 11 Sep 2026 09:19:02 +0800 Subject: [PATCH 1/2] LATX, fix: Synchronize SSE FCSR state in softfpu modes In strict softfpu paths, x87 exceptions are maintained by SoftFloat while SSE and AVX operations still use the native FCSR. Pending native flags and the rounding mode can therefore diverge from env->mxcsr across TB, helper, save, restore, and signal boundaries. Add TB-local SSE rounding-mode preparation and merge native exception flags into MXCSR before architectural state observers. Clear or invalidate pending FCSR state after LDMXCSR and restore paths, synchronize signal frames, and preserve the host floating-point environment around x87 libm helpers. This lightweight path covers SOFTFPU=1 and SOFTFPU=2 with native x87 FAST paths disabled, without adding an x87/SSE owner state machine to hard-float execution. Signed-off-by: Zewei Yang --- linux-user/i386/signal.c | 55 +++++++ target/i386/latx/include/env.h | 3 + target/i386/latx/include/translate.h | 10 ++ target/i386/latx/translator/tr-avx-cvt.c | 7 + target/i386/latx/translator/tr-avx.c | 44 ++++++ target/i386/latx/translator/tr-fctrl.c | 134 +++++++++++++++++- target/i386/latx/translator/tr-opnd-process.c | 20 ++- target/i386/latx/translator/tr-simd-cvt.c | 15 +- target/i386/latx/translator/tr-simd-shift.c | 2 + target/i386/latx/translator/tr-simd.c | 26 ++++ target/i386/latx/translator/tr-softfpu.c | 61 +++++++- target/i386/latx/translator/translate.c | 3 + target/i386/tcg/fpu_helper.c | 13 ++ 13 files changed, 383 insertions(+), 10 deletions(-) diff --git a/linux-user/i386/signal.c b/linux-user/i386/signal.c index 857a126699c..424e3a9e06e 100644 --- a/linux-user/i386/signal.c +++ b/linux-user/i386/signal.c @@ -408,6 +408,40 @@ static void sync_ymm_high_from_xmm(CPUX86State *env) #endif #endif +#ifdef CONFIG_LATX +enum { + LATX_FCSR_FLAGS_SHIFT = 16, + LATX_FCSR_FLAG_I = 1u << 0, + LATX_FCSR_FLAG_U = 1u << 1, + LATX_FCSR_FLAG_O = 1u << 2, + LATX_FCSR_FLAG_Z = 1u << 3, + LATX_FCSR_FLAG_V = 1u << 4, + LATX_FCSR_FLAGS_MASK = 0x1f, + LATX_MXCSR_IE = 1u << 0, + LATX_MXCSR_ZE = 1u << 2, + LATX_MXCSR_OE = 1u << 3, + LATX_MXCSR_UE = 1u << 4, + LATX_MXCSR_PE = 1u << 5, +}; + +static uint32_t latx_fcsr_flags_to_mxcsr(uint32_t fcsr) +{ + uint32_t flags = (fcsr >> LATX_FCSR_FLAGS_SHIFT) & + LATX_FCSR_FLAGS_MASK; + + return (flags & LATX_FCSR_FLAG_V ? LATX_MXCSR_IE : 0) | + (flags & LATX_FCSR_FLAG_Z ? LATX_MXCSR_ZE : 0) | + (flags & LATX_FCSR_FLAG_O ? LATX_MXCSR_OE : 0) | + (flags & LATX_FCSR_FLAG_U ? LATX_MXCSR_UE : 0) | + (flags & LATX_FCSR_FLAG_I ? LATX_MXCSR_PE : 0); +} + +static void latx_clear_saved_fcsr_flags(CPUX86State *env) +{ + env->fcsr &= ~(LATX_FCSR_FLAGS_MASK << LATX_FCSR_FLAGS_SHIFT); +} +#endif + /* * Set up a signal frame. */ @@ -415,6 +449,15 @@ static void sync_ymm_high_from_xmm(CPUX86State *env) static void xsave_sigcontext(CPUX86State *env, struct target_fpstate_fxsave *fxsave, abi_ulong fxsave_addr) { +#ifdef CONFIG_LATX + if (option_softfpu) { + /* + * Merge pending native SSE exceptions before saving the guest + * signal frame. Use the saved FCSR, not the live host register. + */ + env->mxcsr |= latx_fcsr_flags_to_mxcsr(env->fcsr); + } +#endif if (!(env->features[FEAT_1_ECX] & CPUID_EXT_XSAVE)) { /* fxsave_addr must be 16 byte aligned for fxsave */ assert(!(fxsave_addr & 0xf)); @@ -751,6 +794,12 @@ static int xrstor_sigcontext(CPUX86State *env, struct target_fpstate_fxsave *fxs } if (tswapl(*(uint32_t *) &fxsave->xfeatures[xfeatures_size]) == TARGET_FP_XSTATE_MAGIC2) { cpu_x86_xrstor(env, fxsave_addr); +#ifdef CONFIG_LATX + if (option_softfpu) { + /* Discard stale flags after restoring guest state. */ + latx_clear_saved_fcsr_flags(env); + } +#endif #ifdef CONFIG_LATX_AVX_OPT sync_ymm_high_from_xmm(env); #endif @@ -761,6 +810,12 @@ static int xrstor_sigcontext(CPUX86State *env, struct target_fpstate_fxsave *fxs } cpu_x86_fxrstor(env, fxsave_addr); +#ifdef CONFIG_LATX + if (option_softfpu) { + /* Discard stale flags after restoring guest state. */ + latx_clear_saved_fcsr_flags(env); + } +#endif return 0; } diff --git a/target/i386/latx/include/env.h b/target/i386/latx/include/env.h index 7ff6c0b7cec..177bf0d721e 100644 --- a/target/i386/latx/include/env.h +++ b/target/i386/latx/include/env.h @@ -58,6 +58,9 @@ typedef struct TRANSLATION_DATA { int curr_top; /* top value (changes when translating) */ + /* Translation-time SSE RM cache for strict SoftFPU paths; reset per TB. */ + bool sse_rounding_prepared; + /* TODO : support static translation */ uint8 curr_ir1_skipped_eflags; /* these eflag calculation can be skipped */ /* (because of flag pattern, etc) */ diff --git a/target/i386/latx/include/translate.h b/target/i386/latx/include/translate.h index 9edc6b95780..a472112acd1 100644 --- a/target/i386/latx/include/translate.h +++ b/target/i386/latx/include/translate.h @@ -1683,6 +1683,16 @@ void update_fcsr_by_cw(IR2_OPND cw); IR2_OPND set_fpu_fcsr_rounding_field_by_x86(void); void set_fpu_rounding_mode(IR2_OPND rm); +/* + * Lightweight SSE/AVX FCSR synchronization for SoftFPU modes without native + * x87 fast paths. x87 exceptions are maintained by SoftFloat in env->fpus, + * so native FCSR0 carries only pending SSE/AVX state. This is not a complete + * cross-domain scheme when LATX_SOFTFPU_FAST enables native x87 operations. + */ +void prepare_sse_rounding_mode(void); +void submit_sse_flags_to_mxcsr(IR2_OPND mxcsr_opnd); +void clear_sse_fcsr_flags(void); + int generate_native_rotate_fpu_by(void *code_buf); void generate_context_switch_bt_to_native(void *code_buf); void generate_context_switch_native_to_bt(void); diff --git a/target/i386/latx/translator/tr-avx-cvt.c b/target/i386/latx/translator/tr-avx-cvt.c index e903ceeddd4..585f1091301 100644 --- a/target/i386/latx/translator/tr-avx-cvt.c +++ b/target/i386/latx/translator/tr-avx-cvt.c @@ -45,6 +45,7 @@ bool translate_vcvtpd2ps(IR1_INST * pir1) { return translate_vcvtpd2ps_lsx(pir1); } + prepare_sse_rounding_mode(); lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); if (ir1_opnd_size(ir1_get_opnd(pir1, 1)) == 128) { @@ -73,6 +74,7 @@ bool translate_vcvtdq2ps(IR1_INST * pir1) { return translate_vcvtdq2ps_lsx(pir1); } + prepare_sse_rounding_mode(); lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0)) || ir1_opnd_is_ymm(ir1_get_opnd(pir1, 0))); @@ -130,6 +132,7 @@ bool translate_vcvtps2dq(IR1_INST * pir1) { if (!option_enable_lasx) { return translate_vcvtps2dq_lsx(pir1); } + prepare_sse_rounding_mode(); if (option_cvt_opt) { return translate_vcvtps2dq_opt(pir1); @@ -429,6 +432,7 @@ bool translate_vcvtpd2dq(IR1_INST * pir1) { if (!option_enable_lasx) { return translate_vcvtpd2dq_lsx(pir1); } + prepare_sse_rounding_mode(); if (option_cvt_opt) { return translate_vcvtpd2dq_opt(pir1); @@ -585,6 +589,9 @@ bool translate_vcvtsi2sd(IR1_INST * pir1) { lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0)) && ir1_opnd_is_xmm(ir1_get_opnd(pir1, 1))); IR1_OPND * opnd2 = ir1_get_opnd(pir1, 2); + if (ir1_opnd_size(opnd2) == 64) { + prepare_sse_rounding_mode(); + } IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src1 = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); IR2_OPND src2 = load_ireg_from_ir1(opnd2, UNKNOWN_EXTENSION, false); diff --git a/target/i386/latx/translator/tr-avx.c b/target/i386/latx/translator/tr-avx.c index 7c809154e59..2c8f900de69 100644 --- a/target/i386/latx/translator/tr-avx.c +++ b/target/i386/latx/translator/tr-avx.c @@ -14,6 +14,7 @@ #ifdef CONFIG_LATX_AVX_OPT bool translate_vaddpd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vaddpd_lsx(pir1); } @@ -44,6 +45,7 @@ bool translate_vaddpd(IR1_INST * pir1) { } bool translate_vaddps(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vaddps_lsx(pir1); } @@ -74,6 +76,7 @@ bool translate_vaddps(IR1_INST * pir1) { } bool translate_vaddsd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vaddsd_lsx(pir1); } @@ -93,6 +96,7 @@ bool translate_vaddsd(IR1_INST * pir1) { } bool translate_vaddss(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vaddss_lsx(pir1); } @@ -115,6 +119,7 @@ bool translate_vaddss(IR1_INST * pir1) { } bool translate_vsubpd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vsubpd_lsx(pir1); } @@ -145,6 +150,7 @@ bool translate_vsubpd(IR1_INST * pir1) { } bool translate_vsubps(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vsubps_lsx(pir1); } @@ -175,6 +181,7 @@ bool translate_vsubps(IR1_INST * pir1) { } bool translate_vsubsd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vsubsd_lsx(pir1); } @@ -195,6 +202,7 @@ bool translate_vsubsd(IR1_INST * pir1) { } bool translate_vsubss(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vsubss_lsx(pir1); } @@ -217,6 +225,7 @@ bool translate_vsubss(IR1_INST * pir1) { } bool translate_vmulpd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vmulpd_lsx(pir1); } @@ -247,6 +256,7 @@ bool translate_vmulpd(IR1_INST * pir1) { } bool translate_vmulps(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vmulps_lsx(pir1); } @@ -277,6 +287,7 @@ bool translate_vmulps(IR1_INST * pir1) { } bool translate_vmulsd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vmulsd_lsx(pir1); } @@ -295,6 +306,7 @@ bool translate_vmulsd(IR1_INST * pir1) { } bool translate_vmulss(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vmulss_lsx(pir1); } @@ -317,6 +329,7 @@ bool translate_vmulss(IR1_INST * pir1) { } bool translate_vdivpd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vdivpd_lsx(pir1); } @@ -347,6 +360,7 @@ bool translate_vdivpd(IR1_INST * pir1) { } bool translate_vdivps(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vdivps_lsx(pir1); } @@ -377,6 +391,7 @@ bool translate_vdivps(IR1_INST * pir1) { } bool translate_vdivsd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vdivsd_lsx(pir1); } @@ -394,6 +409,7 @@ bool translate_vdivsd(IR1_INST * pir1) { } bool translate_vdivss(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vdivss_lsx(pir1); } @@ -416,6 +432,7 @@ bool translate_vdivss(IR1_INST * pir1) { } bool translate_vsqrtpd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vsqrtpd_lsx(pir1); } @@ -441,6 +458,7 @@ bool translate_vsqrtpd(IR1_INST * pir1) { } bool translate_vsqrtps(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vsqrtps_lsx(pir1); } @@ -466,6 +484,7 @@ bool translate_vsqrtps(IR1_INST * pir1) { } bool translate_vsqrtsd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vsqrtsd_lsx(pir1); } @@ -488,6 +507,7 @@ bool translate_vsqrtsd(IR1_INST * pir1) { } bool translate_vsqrtss(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vsqrtss_lsx(pir1); } @@ -510,6 +530,7 @@ bool translate_vsqrtss(IR1_INST * pir1) { } bool translate_vaddsubpd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vaddsubpd_lsx(pir1); } @@ -546,6 +567,7 @@ bool translate_vaddsubpd(IR1_INST * pir1) { } bool translate_vaddsubps(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vaddsubps_lsx(pir1); } @@ -583,6 +605,7 @@ bool translate_vaddsubps(IR1_INST * pir1) { } bool translate_vhaddpd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vhaddpd_lsx(pir1); } @@ -621,6 +644,7 @@ bool translate_vhaddpd(IR1_INST * pir1) { } bool translate_vhaddps(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vhaddps_lsx(pir1); } @@ -659,6 +683,7 @@ bool translate_vhaddps(IR1_INST * pir1) { } bool translate_vhsubpd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vhsubpd_lsx(pir1); } @@ -697,6 +722,7 @@ bool translate_vhsubpd(IR1_INST * pir1) { } bool translate_vhsubps(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vhsubps_lsx(pir1); } @@ -2547,6 +2573,7 @@ bool translate_vpxor(IR1_INST * pir1) { } bool translate_vfmaddxxxss(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfmaddxxxss_lsx(pir1); } @@ -2581,6 +2608,7 @@ bool translate_vfmaddxxxss(IR1_INST * pir1) { } bool translate_vfmaddxxxsd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfmaddxxxsd_lsx(pir1); } @@ -2615,6 +2643,7 @@ bool translate_vfmaddxxxsd(IR1_INST * pir1) { } bool translate_vfmaddxxxpd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfmaddxxxpd_lsx(pir1); } @@ -2655,6 +2684,7 @@ bool translate_vfmaddxxxpd(IR1_INST * pir1) { } bool translate_vfmaddxxxps(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfmaddxxxps_lsx(pir1); } @@ -2695,6 +2725,7 @@ bool translate_vfmaddxxxps(IR1_INST * pir1) { } bool translate_vfmsubxxxss(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfmsubxxxss_lsx(pir1); } @@ -2729,6 +2760,7 @@ bool translate_vfmsubxxxss(IR1_INST * pir1) { } bool translate_vfmsubxxxsd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfmsubxxxsd_lsx(pir1); } @@ -2763,6 +2795,7 @@ bool translate_vfmsubxxxsd(IR1_INST * pir1) { } bool translate_vfmsubxxxpd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfmsubxxxpd_lsx(pir1); } @@ -2803,6 +2836,7 @@ bool translate_vfmsubxxxpd(IR1_INST * pir1) { } bool translate_vfmsubxxxps(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfmsubxxxps_lsx(pir1); } @@ -2843,6 +2877,7 @@ bool translate_vfmsubxxxps(IR1_INST * pir1) { } bool translate_vfnmaddxxxss(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfnmaddxxxss_lsx(pir1); } @@ -2890,6 +2925,7 @@ bool translate_vfnmaddxxxss(IR1_INST * pir1) { } bool translate_vfnmaddxxxsd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfnmaddxxxsd_lsx(pir1); } @@ -2937,6 +2973,7 @@ bool translate_vfnmaddxxxsd(IR1_INST * pir1) { } bool translate_vfnmaddxxxpd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfnmaddxxxpd_lsx(pir1); } @@ -3005,6 +3042,7 @@ bool translate_vfnmaddxxxpd(IR1_INST * pir1) { } bool translate_vfnmaddxxxps(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfnmaddxxxps_lsx(pir1); } @@ -3072,6 +3110,7 @@ bool translate_vfnmaddxxxps(IR1_INST * pir1) { } bool translate_vfnmsubxxxss(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfnmsubxxxss_lsx(pir1); } @@ -3119,6 +3158,7 @@ bool translate_vfnmsubxxxss(IR1_INST * pir1) { } bool translate_vfnmsubxxxsd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfnmsubxxxsd_lsx(pir1); } @@ -3167,6 +3207,7 @@ bool translate_vfnmsubxxxsd(IR1_INST * pir1) { } bool translate_vfnmsubxxxpd(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfnmsubxxxpd_lsx(pir1); } @@ -3233,6 +3274,7 @@ bool translate_vfnmsubxxxpd(IR1_INST * pir1) { } bool translate_vfnmsubxxxps(IR1_INST * pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vfnmsubxxxps_lsx(pir1); } @@ -4925,6 +4967,7 @@ bool translate_vpavgw(IR1_INST * pir1) { bool translate_vdppd(IR1_INST *pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vdppd_lsx(pir1); } @@ -4974,6 +5017,7 @@ bool translate_vdppd(IR1_INST *pir1) bool translate_vdpps(IR1_INST *pir1) { + prepare_sse_rounding_mode(); if (!option_enable_lasx) { return translate_vdpps_lsx(pir1); } diff --git a/target/i386/latx/translator/tr-fctrl.c b/target/i386/latx/translator/tr-fctrl.c index 36773c5f273..36e5268b682 100644 --- a/target/i386/latx/translator/tr-fctrl.c +++ b/target/i386/latx/translator/tr-fctrl.c @@ -10,6 +10,118 @@ #include "latx-options.h" #include "translate.h" +/* + * Map LoongArch FCSR0 sticky Flags V/Z/O/U/I to the common x86 + * exception-status layout IE/ZE/OE/UE/PE. Bit 1 (DE) remains clear + * because FCSR0 has no directly corresponding flag. + * + * This only emits register operations. It neither accesses guest + * shadow state nor clears FCSR0. + */ +static void fcsr_flags_to_x86_exceptions(IR2_OPND dest, IR2_OPND fcsr) +{ + /* After bit reversal and shifting: dest[4:0] = I/U/O/Z/V. */ + la_bitrev_w(dest, fcsr); + la_srli_d(dest, dest, 31 - FCSR_OFF_FLAGS_V); + + /* Preserve V as x86 IE at bit 0. fcsr is scratch from here on. */ + la_bstrpick_d(fcsr, dest, 0, 0); + + /* I/U/O/Z move from dest[4:1] to x86 PE/UE/OE/ZE at bits 5:2. + * Bit 1, x86 DE, deliberately remains clear. */ + la_bstrpick_d(dest, dest, 4, 1); + la_slli_d(dest, dest, 2); + la_or(dest, dest, fcsr); +} + +/* Merge the native FCSR sticky flags into an x86 flag-bearing value. */ +static void merge_fcsr_flags(IR2_OPND value) +{ + IR2_OPND fcsr = ra_alloc_itemp(); + IR2_OPND x86_exceptions = ra_alloc_itemp(); + + la_movfcsr2gr(fcsr, fcsr_ir2_opnd); + fcsr_flags_to_x86_exceptions(x86_exceptions, fcsr); + la_or(value, value, x86_exceptions); + + ra_free_temp(x86_exceptions); + ra_free_temp(fcsr); +} + +/* + * Merge the native FCSR sticky flags into the SSE/MXCSR shadow. The merged + * value is left in mxcsr_opnd and also written back to env->mxcsr. + */ +void submit_sse_flags_to_mxcsr(IR2_OPND mxcsr_opnd) +{ + int mxcsr_offset = lsenv_offset_of_mxcsr(lsenv); + + lsassert(mxcsr_offset <= 0x7ff); + la_ld_wu(mxcsr_opnd, env_ir2_opnd, mxcsr_offset); + merge_fcsr_flags(mxcsr_opnd); + la_st_w(mxcsr_opnd, env_ir2_opnd, mxcsr_offset); +} + +/* Clear only the native sticky exception flags, preserving FCSR controls. */ +void clear_sse_fcsr_flags(void) +{ + IR2_OPND fcsr = ra_alloc_itemp(); + + la_movfcsr2gr(fcsr, fcsr_ir2_opnd); + la_bstrins_w(fcsr, zero_ir2_opnd, + FCSR_OFF_FLAGS_V, FCSR_OFF_FLAGS_I); + la_movgr2fcsr(fcsr_ir2_opnd, fcsr); + + ra_free_temp(fcsr); +} + +/* + * Load the SSE rounding mode (MXCSR.RC) into FCSR0.RM. + * + * In softfpu modes the native FCSR0 carries pending SSE/AVX state, so its RM + * must follow MXCSR.RC before a native SSE/AVX operation. A TB-local cache + * suppresses redundant loads for consecutive operations; state restore paths + * reset it because they may change MXCSR.RC. + */ +void prepare_sse_rounding_mode(void) +{ + TRANSLATION_DATA *tr_data = lsenv->tr_data; + IR2_OPND mxcsr; + IR2_OPND fcsr; + IR2_OPND no_toggle; + + if (!option_softfpu) { + return; + } + + if (tr_data->sse_rounding_prepared) { + return; + } + + mxcsr = ra_alloc_itemp(); + fcsr = ra_alloc_itemp(); + no_toggle = ra_alloc_label(); + + /* x86 RC -> LoongArch RM: + * 00 RN -> 00, 01 RD -> 11, 10 RU -> 10, 11 RZ -> 01. + * Toggle bit 1 when the low bit is set. */ + la_ld_wu(mxcsr, env_ir2_opnd, lsenv_offset_of_mxcsr(lsenv)); + la_bstrpick_w(mxcsr, mxcsr, 14, 13); + la_andi(fcsr, mxcsr, 1); + la_beqz(fcsr, no_toggle); + la_xori(mxcsr, mxcsr, 2); + la_label(no_toggle); + + la_movfcsr2gr(fcsr, fcsr_ir2_opnd); + la_bstrins_w(fcsr, mxcsr, FCSR_OFF_RM + 1, FCSR_OFF_RM); + la_movgr2fcsr(fcsr_ir2_opnd, fcsr); + + ra_free_temp(fcsr); + ra_free_temp(mxcsr); + + tr_data->sse_rounding_prepared = true; +} + static void update_fcsr_flag(IR2_OPND status_word, IR2_OPND fcsr) { IR2_OPND temp = ra_alloc_itemp(); @@ -209,10 +321,18 @@ bool translate_stmxcsr(IR1_INST *pir1) { /* 1. load the value of the mxcsr register state from env */ IR2_OPND mxcsr_opnd = ra_alloc_itemp(); - int offset = lsenv_offset_of_mxcsr(lsenv); - lsassert(offset <= 0x7ff); - la_ld_wu(mxcsr_opnd, env_ir2_opnd, offset); + if (option_softfpu) { + /* Merge the pending SSE native FCSR flags into env->mxcsr, then + * clear the native sticky flags so they are not re-merged. */ + submit_sse_flags_to_mxcsr(mxcsr_opnd); + clear_sse_fcsr_flags(); + } else { + int offset = lsenv_offset_of_mxcsr(lsenv); + + lsassert(offset <= 0x7ff); + la_ld_wu(mxcsr_opnd, env_ir2_opnd, offset); + } /* 2. store the value of the mxcsr register state to the dest_opnd */ store_ireg_to_ir1(mxcsr_opnd, ir1_get_opnd(pir1, 0), false); @@ -232,6 +352,14 @@ bool translate_ldmxcsr(IR1_INST *pir1) lsassert(offset <= 0x7ff); la_st_w(new_mxcsr, env_ir2_opnd, offset); + if (option_softfpu) { + /* LDMXCSR replaces the architectural MXCSR, discarding the pending + * native FCSR flags and possibly changing MXCSR.RC. The next SSE + * instruction re-syncs FCSR0.RM from the new value. */ + clear_sse_fcsr_flags(); + lsenv->tr_data->sse_rounding_prepared = false; + } + tr_gen_call_to_helper1((ADDR)update_mxcsr_status, 1, LOAD_HELPER_UPDATE_MXCSR_STATUS); diff --git a/target/i386/latx/translator/tr-opnd-process.c b/target/i386/latx/translator/tr-opnd-process.c index ea22e04067b..c35149c2d9a 100644 --- a/target/i386/latx/translator/tr-opnd-process.c +++ b/target/i386/latx/translator/tr-opnd-process.c @@ -1254,11 +1254,25 @@ void store_freg_to_ir1(IR2_OPND opnd2, IR1_OPND *opnd1, bool is_xmm_hi, } } -/* save old fcsr in fcsr_opnd temporary register for reload , then set fcsr - * according to x86 MXCSR register */ +/* + * Prepare native FCSR for a legacy RM-dependent operation. + * + * Softfpu modes use the TB-local SSE preparation path and keep FCSR0 live for + * SSE/AVX operations. Hard-float mode retains the historical temporary + * save/set/restore protocol below. + */ IR2_OPND set_fpu_fcsr_rounding_field_by_x86(void) { + /* + * Softfpu modes use native FCSR0 for SSE/AVX. Use the TB-local + * synchronization path instead of the legacy save/restore sequence. + */ + if (option_softfpu) { + prepare_sse_rounding_mode(); + return zero_ir2_opnd; + } + if (option_set_rounding_opt) return zero_ir2_opnd; IR2_OPND fcsr_opnd = ra_alloc_itemp_internal(); @@ -1288,6 +1302,8 @@ IR2_OPND set_fpu_fcsr_rounding_field_by_x86(void) void set_fpu_rounding_mode(IR2_OPND rm) { + /* Softfpu paths keep the SSE RM and sticky flags live in FCSR0. */ + if (option_softfpu) return; if (option_set_rounding_opt) return; la_movgr2fcsr(fcsr3_ir2_opnd, rm); } diff --git a/target/i386/latx/translator/tr-simd-cvt.c b/target/i386/latx/translator/tr-simd-cvt.c index 97f4850f6ea..b20a7910976 100644 --- a/target/i386/latx/translator/tr-simd-cvt.c +++ b/target/i386/latx/translator/tr-simd-cvt.c @@ -14,11 +14,9 @@ bool translate_cvtdq2pd(IR1_INST *pir1) { lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); - IR2_OPND fcsr_opnd = set_fpu_fcsr_rounding_field_by_x86(); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); la_vffintl_d_w(dest, src); - set_fpu_rounding_mode(fcsr_opnd); return true; } @@ -27,6 +25,7 @@ bool translate_cvtdq2ps(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); la_vffint_s_w(dest, src); return true; } @@ -279,6 +278,7 @@ static bool translate_cvtpd2dq_opt(IR1_INST *pir1) bool translate_cvtpd2dq(IR1_INST *pir1) { + prepare_sse_rounding_mode(); if (option_cvt_opt) { return translate_cvtpd2dq_opt(pir1); } @@ -409,6 +409,7 @@ static bool translate_cvtps2dq_opt(IR1_INST *pir1) bool translate_cvtps2dq(IR1_INST *pir1) { + prepare_sse_rounding_mode(); if (option_cvt_opt) { return translate_cvtps2dq_opt(pir1); } @@ -535,6 +536,7 @@ static bool translate_cvtpd2pi_opt(IR1_INST *pir1) /* refer to cvtps2pi */ bool translate_cvtpd2pi(IR1_INST *pir1) { + prepare_sse_rounding_mode(); if (option_cvt_opt) { return translate_cvtpd2pi_opt(pir1); } @@ -739,6 +741,7 @@ bool translate_cvttpd2pi(IR1_INST *pir1) bool translate_cvtpd2ps(IR1_INST *pir1) { lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); + prepare_sse_rounding_mode(); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); la_vfcvt_s_d(dest, src, src); @@ -771,7 +774,6 @@ bool translate_cvtpi2pd(IR1_INST *pir1) { lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); tr_x87_to_mmx(); - IR2_OPND fcsr_opnd = set_fpu_fcsr_rounding_field_by_x86(); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg_from_ir1_1(ir1_get_opnd(pir1, 1), false, IS_INTEGER); @@ -788,7 +790,6 @@ bool translate_cvtpi2pd(IR1_INST *pir1) la_vextrins_d(dest, temp, 1 << 4); la_vextrins_d(dest, temp0, 0); } - set_fpu_rounding_mode(fcsr_opnd); return true; } @@ -827,6 +828,7 @@ static bool translate_cvtps2pi_opt(IR1_INST *pir1) bool translate_cvtps2pi(IR1_INST *pir1) { + prepare_sse_rounding_mode(); if (option_cvt_opt) { return translate_cvtps2pi_opt(pir1); } @@ -1057,6 +1059,9 @@ bool translate_cvtsi2sd(IR1_INST *pir1) IR1_OPND *opnd2 = ir1_get_opnd(pir1, 1); /* For si2sd, 32-bit int can convert to FP64 without Round */ lsassert(ir1_opnd_is_xmm(opnd1)); + if (ir1_opnd_size(opnd2) == 64) { + prepare_sse_rounding_mode(); + } IR2_OPND dest = load_freg128_from_ir1(opnd1); IR2_OPND src = load_ireg_from_ir1(opnd2, UNKNOWN_EXTENSION, false); IR2_OPND temp_src = ra_alloc_ftemp(); @@ -1175,6 +1180,8 @@ static bool translate_cvtsx2si_opt(IR1_INST *pir1) IR2_OPND temp_i = ra_alloc_itemp(); IR2_OPND overflow = ra_alloc_ftemp(); + prepare_sse_rounding_mode(); + if (ir1_opcode(pir1) == dt_X86_INS_CVTSD2SI #ifdef CONFIG_LATX_AVX_OPT || ir1_opcode(pir1) == dt_X86_INS_VCVTSD2SI diff --git a/target/i386/latx/translator/tr-simd-shift.c b/target/i386/latx/translator/tr-simd-shift.c index b3a04971bed..0e79e7c8a5c 100644 --- a/target/i386/latx/translator/tr-simd-shift.c +++ b/target/i386/latx/translator/tr-simd-shift.c @@ -484,6 +484,7 @@ bool translate_addsubpd(IR1_INST *pir1) IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); IR2_OPND temp_dest_sub = ra_alloc_ftemp(); IR2_OPND ftemp_src_add = ra_alloc_ftemp(); IR2_OPND ftemp_src_sub = ra_alloc_ftemp(); @@ -524,6 +525,7 @@ bool translate_addsubps(IR1_INST *pir1) IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); IR2_OPND temp_dest_sub = ra_alloc_ftemp(); IR2_OPND ftemp_src_add = ra_alloc_ftemp(); IR2_OPND ftemp_src_sub = ra_alloc_ftemp(); diff --git a/target/i386/latx/translator/tr-simd.c b/target/i386/latx/translator/tr-simd.c index 922ad61593e..eef41fb0279 100644 --- a/target/i386/latx/translator/tr-simd.c +++ b/target/i386/latx/translator/tr-simd.c @@ -677,6 +677,7 @@ bool translate_addps(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); la_vfadd_s(dest, dest, src); return true; } @@ -686,6 +687,7 @@ bool translate_addsd(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); bool restore_zero = SHBR_RESTORE_64(pir1); if (SHBR_ON_64(pir1) || restore_zero) { la_fadd_d(dest, dest, src); @@ -709,6 +711,7 @@ bool translate_addss(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); if (SHBR_ON_32(pir1)) { la_fadd_s(dest, dest, src); } else{ @@ -752,6 +755,7 @@ bool translate_divpd(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); la_vfdiv_d(dest, dest, src); return true; } @@ -761,6 +765,7 @@ bool translate_divps(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); la_vfdiv_s(dest, dest, src); return true; } @@ -770,6 +775,7 @@ bool translate_divsd(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); bool restore_zero = SHBR_RESTORE_64(pir1); if (SHBR_ON_64(pir1) || restore_zero) { la_fdiv_d(dest, dest, src); @@ -793,6 +799,7 @@ bool translate_divss(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); if (SHBR_ON_32(pir1)) { la_fdiv_s(dest, dest, src); } else{ @@ -1032,6 +1039,7 @@ bool translate_mulpd(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); la_vfmul_d(dest, dest, src); return true; } @@ -1041,6 +1049,7 @@ bool translate_mulps(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); la_vfmul_s(dest, dest, src); return true; } @@ -1050,6 +1059,7 @@ bool translate_mulsd(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); bool restore_zero = SHBR_RESTORE_64(pir1); if (SHBR_ON_64(pir1) || restore_zero) { la_fmul_d(dest, dest, src); @@ -1073,6 +1083,7 @@ bool translate_mulss(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); if (SHBR_ON_32(pir1)) { la_fmul_s(dest, dest, src); } else{ @@ -1520,6 +1531,7 @@ bool translate_sqrtpd(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); if (option_enable_lasx) { IR2_OPND temp = ra_alloc_ftemp(); @@ -1541,6 +1553,7 @@ bool translate_sqrtps(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); if (option_enable_lasx) { IR2_OPND temp = ra_alloc_ftemp(); @@ -1562,6 +1575,7 @@ bool translate_addpd(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); la_vfadd_d(dest, dest, src); return true; } @@ -1682,6 +1696,7 @@ bool translate_subss(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); if (SHBR_ON_32(pir1)) { la_fsub_s(dest, dest, src); } else{ @@ -1702,6 +1717,7 @@ bool translate_subsd(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); bool restore_zero = SHBR_RESTORE_64(pir1); if (SHBR_ON_64(pir1) || restore_zero) { la_fsub_d(dest, dest, src); @@ -1725,6 +1741,7 @@ bool translate_subps(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); la_vfsub_s(dest, dest, src); return true; } @@ -1734,6 +1751,7 @@ bool translate_subpd(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); la_vfsub_d(dest, dest, src); return true; } @@ -1743,6 +1761,7 @@ bool translate_sqrtsd(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); bool restore_zero = SHBR_RESTORE_64(pir1); if (SHBR_ON_64(pir1) || restore_zero) { la_fsqrt_d(dest, src); @@ -1767,6 +1786,7 @@ bool translate_sqrtss(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); if (SHBR_ON_32(pir1)) { la_fsqrt_s(dest, src); } else{ @@ -1804,6 +1824,7 @@ bool translate_haddpd(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); IR2_OPND temp1 = ra_alloc_ftemp(); IR2_OPND temp2 = ra_alloc_ftemp(); la_vpickev_d(temp1, src, dest); @@ -1818,6 +1839,7 @@ bool translate_haddps(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); IR2_OPND temp1 = ra_alloc_ftemp(); IR2_OPND temp2 = ra_alloc_ftemp(); /** @@ -1840,6 +1862,7 @@ bool translate_hsubpd(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); IR2_OPND temp1 = ra_alloc_ftemp(); IR2_OPND temp2 = ra_alloc_ftemp(); la_vpickev_d(temp1, src, dest); @@ -1853,6 +1876,7 @@ bool translate_hsubps(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(ir1_get_opnd(pir1, 0))); IR2_OPND dest = load_freg128_from_ir1(ir1_get_opnd(pir1, 0)); IR2_OPND src = load_freg128_from_ir1(ir1_get_opnd(pir1, 1)); + prepare_sse_rounding_mode(); IR2_OPND temp1 = ra_alloc_ftemp(); IR2_OPND temp2 = ra_alloc_ftemp(); la_vpickev_w(temp1, src, dest); @@ -2357,6 +2381,7 @@ bool translate_dpps(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(opnd0) || ir1_opnd_is_ymm(opnd0)); IR2_OPND dest = load_freg128_from_ir1(opnd0); IR2_OPND src1 = load_freg128_from_ir1(opnd1); + prepare_sse_rounding_mode(); IR2_OPND temp1 = ra_alloc_ftemp(); IR2_OPND temp2 = ra_alloc_ftemp(); uint8_t imm = ir1_opnd_uimm(opnd2); @@ -2411,6 +2436,7 @@ bool translate_dppd(IR1_INST *pir1) lsassert(ir1_opnd_is_xmm(opnd0)); IR2_OPND dest = load_freg128_from_ir1(opnd0); IR2_OPND src1 = load_freg128_from_ir1(opnd1); + prepare_sse_rounding_mode(); IR2_OPND temp1 = ra_alloc_ftemp(); IR2_OPND temp2 = ra_alloc_ftemp(); uint8_t imm = ir1_opnd_uimm(opnd2); diff --git a/target/i386/latx/translator/tr-softfpu.c b/target/i386/latx/translator/tr-softfpu.c index 849cb968d01..b980a4311b8 100644 --- a/target/i386/latx/translator/tr-softfpu.c +++ b/target/i386/latx/translator/tr-softfpu.c @@ -1935,6 +1935,9 @@ static bool translate_fldcw_softfpu(IR1_INST *pir1) } else { gen_softfpu_helper2m_16u((ADDR)helper_fldcw, mem_opnd); } + if (option_softfpu) { + lsenv->tr_data->sse_rounding_prepared = false; + } return true; } @@ -2021,6 +2024,9 @@ static bool translate_fldenv_softfpu(IR1_INST *pir1) } else { gen_softfpu_helper3i((ADDR)helper_fldenv, mem_opnd, data32); } + if (option_softfpu) { + lsenv->tr_data->sse_rounding_prepared = false; + } return true; } @@ -2326,6 +2332,9 @@ static bool translate_fninit_softfpu(IR1_INST *pir1) } else { gen_softfpu_helper1((ADDR)helper_fninit); } + if (option_softfpu) { + lsenv->tr_data->sse_rounding_prepared = false; + } return true; } @@ -2475,6 +2484,9 @@ static bool translate_fnsave_softfpu(IR1_INST *pir1) IR2_OPND mem_opnd = convert_mem_no_offset(opnd0); gen_softfpu_helper3i((ADDR)helper_fsave, mem_opnd, data32); } + if (option_softfpu) { + lsenv->tr_data->sse_rounding_prepared = false; + } return true; } @@ -2618,7 +2630,9 @@ static bool translate_frstor_softfpu(IR1_INST *pir1) } else { IR2_OPND mem_opnd = convert_mem_no_offset(opnd0); gen_softfpu_helper3i((ADDR)helper_frstor, mem_opnd, data32); - + } + if (option_softfpu) { + lsenv->tr_data->sse_rounding_prepared = false; } return true; } @@ -2651,6 +2665,9 @@ static bool translate_fsetpm_softfpu(IR1_INST *pir1) static bool translate_fsin_softfpu(IR1_INST *pir1) { gen_softfpu_helper1((ADDR)helper_fsin); + if (option_softfpu) { + lsenv->tr_data->sse_rounding_prepared = false; + } return true; } @@ -3131,6 +3148,13 @@ static bool translate_fxrstor_softfpu(IR1_INST *pir1) IR2_OPND mem_opnd = convert_mem_no_offset(opnd0); gen_softfpu_helper2m_ptr((ADDR)helper_fxrstor, mem_opnd); + + if (option_softfpu) { + /* Discard old flags only after the restore succeeds. */ + clear_sse_fcsr_flags(); + lsenv->tr_data->sse_rounding_prepared = false; + } + return true; } @@ -3139,6 +3163,13 @@ static bool translate_fxsave_softfpu(IR1_INST *pir1) IR1_OPND *opnd0 = ir1_get_opnd(pir1, 0); IR2_OPND mem_opnd = convert_mem_no_offset(opnd0); + if (option_softfpu) { + /* helper_fxsave stores the merged env->mxcsr after alignment check. */ + IR2_OPND mxcsr = ra_alloc_itemp(); + submit_sse_flags_to_mxcsr(mxcsr); + ra_free_temp(mxcsr); + } + gen_softfpu_helper2m_ptr((ADDR)helper_fxsave, mem_opnd); return true; } @@ -3198,6 +3229,13 @@ static bool translate_xsave_softfpu(IR1_INST *pir1) la_bstrins_d(temp_rfbm, eax_opnd, 31, 0); la_bstrins_d(temp_rfbm, edx_opnd, 63, 32); + + if (option_softfpu) { + IR2_OPND mxcsr = ra_alloc_itemp(); + + submit_sse_flags_to_mxcsr(mxcsr); + ra_free_temp(mxcsr); + } gen_softfpu_helper3_ll((ADDR)helper_xsave, mem_opnd, temp_rfbm); return true; } @@ -3212,12 +3250,28 @@ static bool translate_xsaveopt_softfpu(IR1_INST *pir1) la_bstrins_d(temp_rfbm, eax_opnd, 31, 0); la_bstrins_d(temp_rfbm, edx_opnd, 63, 32); + + if (option_softfpu) { + IR2_OPND mxcsr = ra_alloc_itemp(); + + submit_sse_flags_to_mxcsr(mxcsr); + ra_free_temp(mxcsr); + } gen_softfpu_helper3_ll((ADDR)helper_xsaveopt, mem_opnd, temp_rfbm); return true; } static bool translate_xrstor_softfpu(IR1_INST *pir1) { + if (option_softfpu) { + IR2_OPND mxcsr = ra_alloc_itemp(); + + /* Preserve pending flags if RFBM excludes SSE. If SSE is selected, + * helper_xrstor replaces this value with the restored MXCSR. */ + submit_sse_flags_to_mxcsr(mxcsr); + ra_free_temp(mxcsr); + } + IR2_OPND eax_opnd = ra_alloc_gpr(eax_index); IR2_OPND edx_opnd = ra_alloc_gpr(edx_index); IR2_OPND temp_rfbm = ra_alloc_itemp(); @@ -3227,6 +3281,11 @@ static bool translate_xrstor_softfpu(IR1_INST *pir1) la_bstrins_d(temp_rfbm, eax_opnd, 31, 0); la_bstrins_d(temp_rfbm, edx_opnd, 63, 32); gen_softfpu_helper3_ll((ADDR)helper_xrstor, mem_opnd, temp_rfbm); + + if (option_softfpu) { + clear_sse_fcsr_flags(); + lsenv->tr_data->sse_rounding_prepared = false; + } return true; } #endif diff --git a/target/i386/latx/translator/translate.c b/target/i386/latx/translator/translate.c index 38266d7bbcb..eb4e17bb61f 100644 --- a/target/i386/latx/translator/translate.c +++ b/target/i386/latx/translator/translate.c @@ -108,6 +108,9 @@ void tr_init(void *tb) t->curr_tb = tb; t->curr_ir1_inst = NULL; + /* Each TB must establish its own native SSE rounding mode. */ + t->sse_rounding_prepared = false; + /* register allocation init */ ra_free_all(); diff --git a/target/i386/tcg/fpu_helper.c b/target/i386/tcg/fpu_helper.c index 1efc9d70b76..9eb941dc28a 100644 --- a/target/i386/tcg/fpu_helper.c +++ b/target/i386/tcg/fpu_helper.c @@ -1304,6 +1304,8 @@ void helper_f2xm1(CPUX86State *env) void helper_fptan(CPUX86State *env) { #ifdef CONFIG_LATX + fenv_t saved_host_env; + fegetenv(&saved_host_env); long double fptemp = floatx80_to_longdouble(env, ST0); if ((fptemp > MAXTAN) || (fptemp < -MAXTAN)) { @@ -1316,6 +1318,7 @@ void helper_fptan(CPUX86State *env) env->fpus &= ~0x400; /* C2 <-- 0 */ /* the above code is for |arg| < 2**52 only */ } + fesetenv(&saved_host_env); #else double fptemp = floatx80_to_double(env, ST0); @@ -2328,6 +2331,8 @@ void helper_fsqrt(CPUX86State *env) void helper_fsincos(CPUX86State *env) { #ifdef CONFIG_LATX + fenv_t saved_host_env; + fegetenv(&saved_host_env); long double fptemp = floatx80_to_longdouble(env, ST0); if ((fptemp > MAXTAN) || (fptemp < -MAXTAN)) { @@ -2339,6 +2344,7 @@ void helper_fsincos(CPUX86State *env) env->fpus &= ~0x400; /* C2 <-- 0 */ /* the above code is for |arg| < 2**63 only */ } + fesetenv(&saved_host_env); #else double fptemp = floatx80_to_double(env, ST0); @@ -2421,6 +2427,8 @@ void helper_fscale(CPUX86State *env) void helper_fsin(CPUX86State *env) { #ifdef CONFIG_LATX + fenv_t saved_host_env; + fegetenv(&saved_host_env); long double fptemp = floatx80_to_longdouble(env, ST0); if ((fptemp > MAXTAN) || (fptemp < -MAXTAN)) { @@ -2430,6 +2438,7 @@ void helper_fsin(CPUX86State *env) env->fpus &= ~0x400; /* C2 <-- 0 */ /* the above code is for |arg| < 2**53 only */ } + fesetenv(&saved_host_env); #else double fptemp = floatx80_to_double(env, ST0); @@ -2446,6 +2455,8 @@ void helper_fsin(CPUX86State *env) void helper_fcos(CPUX86State *env) { #ifdef CONFIG_LATX + fenv_t saved_host_env; + fegetenv(&saved_host_env); long double fptemp = floatx80_to_longdouble(env, ST0); if ((fptemp > MAXTAN) || (fptemp < -MAXTAN)) { @@ -2455,6 +2466,7 @@ void helper_fcos(CPUX86State *env) env->fpus &= ~0x400; /* C2 <-- 0 */ /* the above code is for |arg| < 2**63 only */ } + fesetenv(&saved_host_env); #else double fptemp = floatx80_to_double(env, ST0); @@ -2856,6 +2868,7 @@ void helper_fxsave(CPUX86State *env, target_ulong ptr) do_xsave_fpu(env, ptr, ra); if (env->cr[4] & CR4_OSFXSR_MASK) { + cpu_stl_data_ra(env, ptr + XO(legacy.mxcsr), env->mxcsr, ra); cpu_stl_data_ra(env, ptr + XO(legacy.mxcsr_mask), 0x0000ffff, ra); /* Fast FXSAVE leaves out the XMM registers */ if (!(env->efer & MSR_EFER_FFXSR) From 07739779710b78581d4a7b6cad1a1b7d37b002bf Mon Sep 17 00:00:00 2001 From: Zewei Yang Date: Fri, 11 Sep 2026 09:21:44 +0800 Subject: [PATCH 2/2] LATX, test: Cover softfpu FCSR synchronization Add freestanding x86_64 guest tests for SSE and x87 exception isolation, rounding-mode restoration, CVT and CVTT status, x87 pop semantics, architectural save and restore instructions, and signal-frame MXCSR handling. Register the five suites with the existing LoongArch LAT integration framework. AVX and XSAVE cases are enabled automatically for builds that provide the corresponding translator support, while LATX_SOFTFPU remains overridable so the same tests can validate SOFTFPU=1 and SOFTFPU=2 with FAST disabled. Signed-off-by: Zewei Yang --- tests/integration/fpu-controls-guest.S | 845 ++++++++++++++++++ tests/integration/fpu-flags-guest.S | 660 ++++++++++++++ tests/integration/fpu-signal-guest.S | 173 ++++ tests/integration/meson.build | 1 + tests/integration/mxcsr-cvt-guest.S | 102 +++ .../integration/registrations/fpu/meson.build | 58 ++ tests/integration/registrations/meson.build | 1 + tests/integration/test-fpu-controls.sh | 174 ++++ tests/integration/test-fpu-flags.sh | 260 ++++++ tests/integration/test-fpu-signal.sh | 76 ++ tests/integration/test-mxcsr-cvt.sh | 99 ++ tests/integration/test-x87-pop.sh | 97 ++ tests/integration/x87-pop-guest.S | 86 ++ 13 files changed, 2632 insertions(+) create mode 100644 tests/integration/fpu-controls-guest.S create mode 100644 tests/integration/fpu-flags-guest.S create mode 100644 tests/integration/fpu-signal-guest.S create mode 100644 tests/integration/mxcsr-cvt-guest.S create mode 100644 tests/integration/registrations/fpu/meson.build create mode 100755 tests/integration/test-fpu-controls.sh create mode 100755 tests/integration/test-fpu-flags.sh create mode 100755 tests/integration/test-fpu-signal.sh create mode 100755 tests/integration/test-mxcsr-cvt.sh create mode 100755 tests/integration/test-x87-pop.sh create mode 100644 tests/integration/x87-pop-guest.S diff --git a/tests/integration/fpu-controls-guest.S b/tests/integration/fpu-controls-guest.S new file mode 100644 index 00000000000..8c1b24d510c --- /dev/null +++ b/tests/integration/fpu-controls-guest.S @@ -0,0 +1,845 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Freestanding x86_64 guest for floating-point control-state tests. + * + * The cases check x87/MXCSR rounding-control ownership across FNINIT, FLDCW, + * XRSTOR, AVX arithmetic, and SSE conversions. Native FCSR is deliberately + * primed where needed so a failure identifies stale shared-FCSR control state + * rather than an unrelated arithmetic result. + */ + +#ifndef TEST_CASE +#error TEST_CASE must select an FPU control test +#endif + +.equ __NR_write, 1 +.equ __NR_exit, 60 + +.section .text +.global _start +.type _start, @function +_start: + fninit + +#if TEST_CASE == 1 + /* MXCSR round-up must survive FNINIT. */ + fldcw x87_round_up(%rip) + ldmxcsr mxcsr_round_up(%rip) + fninit + + movss one(%rip), %xmm0 + addss half_ulp(%rip), %xmm0 + movss %xmm0, report(%rip) + +#elif TEST_CASE == 2 + /* x87 RC=round-up must be read from env->fpuc by x87 prepare. */ + fldcw x87_round_up(%rip) + flds one(%rip) + fadds half_ulp(%rip) + fstps report(%rip) + +#elif TEST_CASE == 3 + /* + * Establish the SSE domain, then change native RM through FLDCW. + * The next SSE instruction must invalidate the TB-local domain and + * restore MXCSR's round-up mode. + */ + ldmxcsr mxcsr_round_up(%rip) + movss one(%rip), %xmm0 + addss half_ulp(%rip), %xmm0 + + fldcw x87_round_down(%rip) + + movss one(%rip), %xmm0 + addss half_ulp(%rip), %xmm0 + movss %xmm0, report(%rip) + +#elif TEST_CASE == 4 + /* + * Establish the SSE domain, then FLDENV changes native RM through the + * restored x87 control word. The following SSE operation must re-prepare + * from MXCSR round-up. + */ + ldmxcsr mxcsr_round_up(%rip) + movss one(%rip), %xmm0 + addss half_ulp(%rip), %xmm0 + + fldenv fldenv_area(%rip) + + movss one(%rip), %xmm0 + addss half_ulp(%rip), %xmm0 + movss %xmm0, report(%rip) + +#elif TEST_CASE == 5 + /* FRSTOR must restore the x87 rounding mode into native FCSR. */ + fldcw x87_round_up(%rip) + fnsave frstor_area(%rip) + movw $0x077f, frstor_area(%rip) + fldcw x87_round_up(%rip) + frstor frstor_area(%rip) + + flds one(%rip) + fadds half_ulp(%rip) + fstps report(%rip) + +#elif TEST_CASE == 6 + /* XRSTOR with RFBM=3 must restore the x87 rounding mode. */ + fldcw x87_round_up(%rip) + movl $3, %eax + xorl %edx, %edx + xsave xsave_area(%rip) + movw $0x077f, xsave_area(%rip) + movl $3, %eax + xorl %edx, %edx + xrstor xsave_area(%rip) + + flds one(%rip) + fadds half_ulp(%rip) + fstps report(%rip) + +#elif TEST_CASE == 7 + /* RFBM=1 restores only the x87 component. */ + fldcw x87_round_up(%rip) + movl $1, %eax + xorl %edx, %edx + xsave xsave_area(%rip) + movw $0x077f, xsave_area(%rip) + fldcw x87_round_up(%rip) + movl $1, %eax + xorl %edx, %edx + xrstor xsave_area(%rip) + + flds one(%rip) + fadds half_ulp(%rip) + fstps report(%rip) + +#elif TEST_CASE == 8 + /* RFBM=2 restores only the SSE component and its MXCSR. */ + ldmxcsr mxcsr_round_up(%rip) + movl $2, %eax + xorl %edx, %edx + xsave xsave_area(%rip) + movl $0x00003f80, xsave_area+24(%rip) + ldmxcsr mxcsr_round_up(%rip) + movl $2, %eax + xorl %edx, %edx + xrstor xsave_area(%rip) + + movss one(%rip), %xmm0 + addss half_ulp(%rip), %xmm0 + movss %xmm0, report(%rip) + +#elif TEST_CASE == 9 + /* RFBM=3 restores x87 and SSE; both must use round-down afterwards. */ + fldcw x87_round_up(%rip) + ldmxcsr mxcsr_round_up(%rip) + movl $3, %eax + xorl %edx, %edx + xsave xsave_area(%rip) + movw $0x077f, xsave_area(%rip) + movl $0x00003f80, xsave_area+24(%rip) + fldcw x87_round_up(%rip) + ldmxcsr mxcsr_round_up(%rip) + movl $3, %eax + xorl %edx, %edx + xrstor xsave_area(%rip) + + flds one(%rip) + fadds half_ulp(%rip) + fstps report(%rip) + movss one(%rip), %xmm0 + addss half_ulp(%rip), %xmm0 + movss %xmm0, report+4(%rip) + +#elif TEST_CASE == 10 + /* VADDSS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovss one(%rip), %xmm0 + vaddss half_ulp(%rip), %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 11 + /* VADDPS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovups packed_one(%rip), %xmm0 + vaddps packed_half(%rip), %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 12 + /* VADDSD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovsd one_d(%rip), %xmm0 + vaddsd half_ulp_d(%rip), %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 13 + /* VADDPD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovupd packed_one_d(%rip), %xmm0 + vaddpd packed_half_d(%rip), %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 14 + /* VSUBSS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovss one(%rip), %xmm0 + vsubss neg_half_ulp(%rip), %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 15 + /* VSUBPS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovups packed_one(%rip), %xmm0 + vsubps packed_neg_half(%rip), %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 16 + /* VSUBSD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovsd one_d(%rip), %xmm0 + vsubsd neg_half_ulp_d(%rip), %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 17 + /* VSUBPD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovupd packed_one_d(%rip), %xmm0 + vsubpd packed_neg_half_d(%rip), %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 18 + /* VMULSS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovss one_point25(%rip), %xmm0 + vmulss next_one(%rip), %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 19 + /* VMULPS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovups packed_one_point25(%rip), %xmm0 + vmulps packed_next_one(%rip), %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 20 + /* VMULSD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovsd one_point25_d(%rip), %xmm0 + vmulsd next_one_d(%rip), %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 21 + /* VMULPD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovupd packed_one_point25_d(%rip), %xmm0 + vmulpd packed_next_one_d(%rip), %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 22 + /* VDIVSS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovss div_float_num(%rip), %xmm0 + vdivss div_float_den(%rip), %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 23 + /* VDIVPS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovups packed_div_float_num(%rip), %xmm0 + vdivps packed_div_float_den(%rip), %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 24 + /* VDIVSD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovsd div_double_num(%rip), %xmm0 + vdivsd div_double_den(%rip), %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 25 + /* VDIVPD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovupd packed_div_double_num(%rip), %xmm0 + vdivpd packed_div_double_den(%rip), %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 26 + /* VSQRTSS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovss sqrt_two(%rip), %xmm0 + vsqrtss %xmm0, %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 27 + /* VSQRTPS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovups packed_sqrt_two(%rip), %xmm0 + vsqrtps %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 28 + /* VSQRTSD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovsd sqrt_three_d(%rip), %xmm0 + vsqrtsd %xmm0, %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 29 + /* VSQRTPD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovupd packed_sqrt_three_d(%rip), %xmm0 + vsqrtpd %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 30 + /* VADDSUBPS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovups packed_one(%rip), %xmm0 + vaddsubps packed_neg_half(%rip), %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 31 + /* VADDSUBPD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovupd packed_one_d(%rip), %xmm0 + vaddsubpd packed_neg_half_d(%rip), %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 32 + /* VHADDPS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovups hadd_src1_s(%rip), %xmm0 + vhaddps packed_zero_s(%rip), %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 33 + /* VHADDPD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovupd hadd_src1_d(%rip), %xmm0 + vhaddpd packed_zero_d(%rip), %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 34 + /* VHSUBPS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovups hsub_src1_s(%rip), %xmm0 + vhsubps packed_zero_s(%rip), %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 35 + /* VHSUBPD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovupd hsub_src1_d(%rip), %xmm0 + vhsubpd packed_zero_d(%rip), %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 36 + /* VFMADD132SS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovss one(%rip), %xmm0 + vfmadd132ss half_ulp(%rip), %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 37 + /* VFMADD132PS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovups packed_one(%rip), %xmm0 + vfmadd132ps packed_half(%rip), %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 38 + /* VFMADD132SD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovsd one_d(%rip), %xmm0 + vfmadd132sd half_ulp_d(%rip), %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 39 + /* VFMADD132PD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovupd packed_one_d(%rip), %xmm0 + vfmadd132pd packed_half_d(%rip), %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 40 + /* VFMSUB132SS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovss one(%rip), %xmm0 + vmovss neg_half_ulp(%rip), %xmm1 + vfmsub132ss one(%rip), %xmm1, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 41 + /* VFMSUB132SD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovsd one_d(%rip), %xmm0 + vmovsd neg_half_ulp_d(%rip), %xmm1 + vfmsub132sd one_d(%rip), %xmm1, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 42 + /* VFNMADD132SS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovss one(%rip), %xmm0 + vmovss half_ulp(%rip), %xmm1 + vfnmadd132ss negative_one_s(%rip), %xmm1, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 43 + /* VFNMADD132SD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovsd one_d(%rip), %xmm0 + vmovsd half_ulp_d(%rip), %xmm1 + vfnmadd132sd negative_one_d(%rip), %xmm1, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 44 + /* VFNMSUB132SS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovss one(%rip), %xmm0 + vmovss neg_half_ulp(%rip), %xmm1 + vfnmsub132ss negative_one_s(%rip), %xmm1, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 45 + /* VFNMSUB132SD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovsd one_d(%rip), %xmm0 + vmovsd neg_half_ulp_d(%rip), %xmm1 + vfnmsub132sd negative_one_d(%rip), %xmm1, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 46 + /* VDPPS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovups dpps_src1_s(%rip), %xmm0 + vdpps $0xf1, dpps_src2_s(%rip), %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + +#elif TEST_CASE == 47 + /* VDPPD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + vmovupd dppd_src1_d(%rip), %xmm0 + vdppd $0x31, dppd_src2_d(%rip), %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + +#elif TEST_CASE == 48 + /* HADDPS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + movups hadd_src1_s(%rip), %xmm0 + movups packed_zero_s(%rip), %xmm1 + haddps %xmm1, %xmm0 + movss %xmm0, report(%rip) + +#elif TEST_CASE == 49 + /* HADDPD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + movupd hadd_src1_d(%rip), %xmm0 + movupd packed_zero_d(%rip), %xmm1 + haddpd %xmm1, %xmm0 + movsd %xmm0, report(%rip) + +#elif TEST_CASE == 50 + /* HSUBPS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + movups hsub_src1_s(%rip), %xmm0 + movups packed_zero_s(%rip), %xmm1 + hsubps %xmm1, %xmm0 + movss %xmm0, report(%rip) + +#elif TEST_CASE == 51 + /* HSUBPD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + movupd hsub_src1_d(%rip), %xmm0 + movupd packed_zero_d(%rip), %xmm1 + hsubpd %xmm1, %xmm0 + movsd %xmm0, report(%rip) + +#elif TEST_CASE == 52 + /* DPPS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + movups dpps_src1_s(%rip), %xmm0 + movups dpps_src2_s(%rip), %xmm1 + dpps $0xf1, %xmm1, %xmm0 + movss %xmm0, report(%rip) + +#elif TEST_CASE == 53 + /* DPPD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + movupd dppd_src1_d(%rip), %xmm0 + movupd dppd_src2_d(%rip), %xmm1 + dppd $0x31, %xmm1, %xmm0 + movsd %xmm0, report(%rip) + +#elif TEST_CASE == 54 + /* ADDSUBPS must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + movups packed_one(%rip), %xmm0 + movups packed_neg_half(%rip), %xmm1 + addsubps %xmm1, %xmm0 + movss %xmm0, report(%rip) + +#elif TEST_CASE == 55 + /* ADDSUBPD must use MXCSR's round-up mode. */ + ldmxcsr mxcsr_round_up(%rip) + movupd packed_one_d(%rip), %xmm0 + movupd packed_neg_half_d(%rip), %xmm1 + addsubpd %xmm1, %xmm0 + movsd %xmm0, report(%rip) + +#elif TEST_CASE == 56 + /* CVTDQ2PS must follow MXCSR.RC for an exactly halfway integer. */ + ldmxcsr mxcsr_round_nearest(%rip) + movups cvt_dq_input(%rip), %xmm0 + cvtdq2ps %xmm0, %xmm0 + movss %xmm0, report(%rip) + + ldmxcsr mxcsr_round_up(%rip) + movups cvt_dq_input(%rip), %xmm0 + cvtdq2ps %xmm0, %xmm0 + movss %xmm0, report+4(%rip) + +#elif TEST_CASE == 57 + /* CVTSD2SS must follow MXCSR.RC when narrowing double to float. */ + ldmxcsr mxcsr_round_nearest(%rip) + movsd cvt_sd_halfway(%rip), %xmm0 + cvtsd2ss %xmm0, %xmm0 + movss %xmm0, report(%rip) + + ldmxcsr mxcsr_round_up(%rip) + movsd cvt_sd_halfway(%rip), %xmm0 + cvtsd2ss %xmm0, %xmm0 + movss %xmm0, report+4(%rip) + +#elif TEST_CASE == 58 + /* CVTSS2SI must follow MXCSR.RC for a non-integral source. */ + ldmxcsr mxcsr_round_nearest(%rip) + movss cvt_ss_one_point_five(%rip), %xmm0 + cvtss2si %xmm0, %eax + movl %eax, report(%rip) + + ldmxcsr mxcsr_round_down(%rip) + movss cvt_ss_one_point_five(%rip), %xmm0 + cvtss2si %xmm0, %eax + movl %eax, report+4(%rip) + +#elif TEST_CASE == 59 + /* CVTTSS2SI is fixed round-toward-zero and must ignore MXCSR.RC. */ + ldmxcsr mxcsr_round_nearest(%rip) + movss cvt_ss_one_point_seven_five(%rip), %xmm0 + cvttss2si %xmm0, %eax + movl %eax, report(%rip) + + ldmxcsr mxcsr_round_up(%rip) + movss cvt_ss_one_point_seven_five(%rip), %xmm0 + cvttss2si %xmm0, %eax + movl %eax, report+4(%rip) + +#elif TEST_CASE == 60 + /* CVTPS2DQ must follow MXCSR.RC for a non-integral source. */ + ldmxcsr mxcsr_round_nearest(%rip) + movups cvt_packed_ss_one_point_five(%rip), %xmm0 + cvtps2dq %xmm0, %xmm0 + movd %xmm0, %eax + movl %eax, report(%rip) + + ldmxcsr mxcsr_round_down(%rip) + movups cvt_packed_ss_one_point_five(%rip), %xmm0 + cvtps2dq %xmm0, %xmm0 + movd %xmm0, %eax + movl %eax, report+4(%rip) + +#elif TEST_CASE == 61 + /* CVTPD2DQ must follow MXCSR.RC for a non-integral source. */ + ldmxcsr mxcsr_round_nearest(%rip) + movupd cvt_packed_d_one_point_five(%rip), %xmm0 + cvtpd2dq %xmm0, %xmm0 + movd %xmm0, %eax + movl %eax, report(%rip) + + ldmxcsr mxcsr_round_down(%rip) + movupd cvt_packed_d_one_point_five(%rip), %xmm0 + cvtpd2dq %xmm0, %xmm0 + movd %xmm0, %eax + movl %eax, report+4(%rip) + +#elif TEST_CASE == 62 + /* CVTPD2PS must follow MXCSR.RC when narrowing double to float. */ + ldmxcsr mxcsr_round_nearest(%rip) + movupd cvt_packed_d_halfway(%rip), %xmm0 + cvtpd2ps %xmm0, %xmm0 + movss %xmm0, report(%rip) + + ldmxcsr mxcsr_round_up(%rip) + movupd cvt_packed_d_halfway(%rip), %xmm0 + cvtpd2ps %xmm0, %xmm0 + movss %xmm0, report+4(%rip) + +#elif TEST_CASE == 63 + /* CVTSI2SS must follow MXCSR.RC for a large integer source. */ + ldmxcsr mxcsr_round_nearest(%rip) + movl $0x01000001, %eax + cvtsi2ss %eax, %xmm0 + movss %xmm0, report(%rip) + + ldmxcsr mxcsr_round_up(%rip) + movl $0x01000001, %eax + cvtsi2ss %eax, %xmm0 + movss %xmm0, report+4(%rip) + +#elif TEST_CASE == 64 + /* VCVTPD2PS must follow MXCSR.RC when narrowing double to float. */ + ldmxcsr mxcsr_round_nearest(%rip) + vmovupd cvt_packed_d_halfway(%rip), %xmm0 + vcvtpd2ps %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + + ldmxcsr mxcsr_round_up(%rip) + vmovupd cvt_packed_d_halfway(%rip), %xmm0 + vcvtpd2ps %xmm0, %xmm0 + vmovss %xmm0, report+4(%rip) + +#elif TEST_CASE == 65 + /* VCVTDQ2PS must follow MXCSR.RC for an exactly halfway integer. */ + ldmxcsr mxcsr_round_nearest(%rip) + vmovups cvt_dq_input(%rip), %xmm0 + vcvtdq2ps %xmm0, %xmm0 + vmovss %xmm0, report(%rip) + + ldmxcsr mxcsr_round_up(%rip) + vmovups cvt_dq_input(%rip), %xmm0 + vcvtdq2ps %xmm0, %xmm0 + vmovss %xmm0, report+4(%rip) + +#elif TEST_CASE == 66 + /* VCVTPS2DQ must follow MXCSR.RC for a non-integral source. */ + ldmxcsr mxcsr_round_nearest(%rip) + vmovups cvt_packed_ss_one_point_five(%rip), %xmm0 + vcvtps2dq %xmm0, %xmm0 + vmovd %xmm0, %eax + movl %eax, report(%rip) + + ldmxcsr mxcsr_round_down(%rip) + vmovups cvt_packed_ss_one_point_five(%rip), %xmm0 + vcvtps2dq %xmm0, %xmm0 + vmovd %xmm0, %eax + movl %eax, report+4(%rip) + +#elif TEST_CASE == 67 + /* VCVTPD2DQ must follow MXCSR.RC for a non-integral source. */ + ldmxcsr mxcsr_round_nearest(%rip) + vmovupd cvt_packed_d_one_point_five(%rip), %xmm0 + vcvtpd2dq %xmm0, %xmm0 + vmovd %xmm0, %eax + movl %eax, report(%rip) + + ldmxcsr mxcsr_round_down(%rip) + vmovupd cvt_packed_d_one_point_five(%rip), %xmm0 + vcvtpd2dq %xmm0, %xmm0 + vmovd %xmm0, %eax + movl %eax, report+4(%rip) + +#elif TEST_CASE == 68 + /* 64-bit VCVTSI2SD must follow MXCSR.RC when the integer is inexact. */ + ldmxcsr mxcsr_round_nearest(%rip) + movq cvt_si2sd_input(%rip), %rax + vcvtsi2sd %rax, %xmm0, %xmm0 + vmovsd %xmm0, report(%rip) + + ldmxcsr mxcsr_round_up(%rip) + movq cvt_si2sd_input(%rip), %rax + vcvtsi2sd %rax, %xmm0, %xmm0 + vmovsd %xmm0, report+8(%rip) + +#elif TEST_CASE == 69 || TEST_CASE == 70 || TEST_CASE == 71 + /* + * Prime the TB-local SSE RM cache before crossing the helper boundary. + * With LATX_ROUNDING_OPT=1, FNSAVE/FSAVE reset host RM to nearest; + * FRSTOR loads round-down. Neither may change subsequent SSE round-up. + * Keep the measured sequence straight-line to exercise the same TB. + */ +#if TEST_CASE == 71 + /* Create a valid empty x87 image before priming the SSE cache. */ + fnsave frstor_area(%rip) + movw $0x077f, frstor_area(%rip) +#endif + ldmxcsr mxcsr_round_up(%rip) + movss one(%rip), %xmm0 + addss half_ulp(%rip), %xmm0 + +#if TEST_CASE == 69 + fnsave frstor_area(%rip) +#elif TEST_CASE == 70 + /* FSAVE encodes FWAIT followed by FNSAVE. */ + fsave frstor_area(%rip) +#else + frstor frstor_area(%rip) +#endif + + movss one(%rip), %xmm0 + addss half_ulp(%rip), %xmm0 + movss %xmm0, report(%rip) + +#else +#error unsupported TEST_CASE +#endif + + /* Cases 9, 12, 13, 16, 17, 20, 21, 24, 25, 28, 29, 31, 33, 35, 38, 39, 41, 43, 45, 47, 49, 51, 53, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, and 67 report 8 bytes; case 68 reports 16 bytes; all other cases report 4. */ + movl $__NR_write, %eax + movl $1, %edi + leaq report(%rip), %rsi +#if TEST_CASE == 9 || TEST_CASE == 12 || TEST_CASE == 13 || \ + TEST_CASE == 16 || TEST_CASE == 17 || TEST_CASE == 20 || TEST_CASE == 21 || \ + TEST_CASE == 24 || TEST_CASE == 25 || TEST_CASE == 28 || TEST_CASE == 29 || \ + TEST_CASE == 31 || TEST_CASE == 33 || TEST_CASE == 35 || \ + TEST_CASE == 38 || TEST_CASE == 39 || TEST_CASE == 41 || \ + TEST_CASE == 43 || TEST_CASE == 45 || TEST_CASE == 47 || \ + TEST_CASE == 49 || TEST_CASE == 51 || TEST_CASE == 53 || TEST_CASE == 55 || \ + TEST_CASE == 56 || TEST_CASE == 57 || TEST_CASE == 58 || TEST_CASE == 59 || \ + TEST_CASE == 60 || TEST_CASE == 61 || TEST_CASE == 62 || TEST_CASE == 63 || \ + TEST_CASE == 64 || TEST_CASE == 65 || TEST_CASE == 66 || TEST_CASE == 67 + movl $8, %edx +#elif TEST_CASE == 68 + movl $16, %edx +#else + movl $4, %edx +#endif + syscall + + xorl %edi, %edi + movl $__NR_exit, %eax + syscall +.size _start, .-_start + +.section .rodata +.align 2 +x87_round_up: + .word 0x0b7f +x87_round_down: + .word 0x077f +mxcsr_round_up: + .long 0x00005f80 +mxcsr_round_nearest: + .long 0x00001f80 +mxcsr_round_down: + .long 0x00003f80 +one: + .long 0x3f800000 +negative_one_s: + .long 0xbf800000 +one_point25: + .long 0x3fa00000 +next_one: + .long 0x3f800001 +cvt_dq_input: + .long 0x01000001, 0x00000000, 0x00000000, 0x00000000 +cvt_sd_halfway: + .quad 0x3ff0000010000000 +cvt_ss_one_point_five: + .long 0x3fc00000 +cvt_ss_one_point_seven_five: + .long 0x3fe00000 +cvt_packed_ss_one_point_five: + .long 0x3fc00000, 0x00000000, 0x00000000, 0x00000000 +cvt_packed_d_one_point_five: + .quad 0x3ff8000000000000, 0x0000000000000000 +cvt_packed_d_halfway: + .quad 0x3ff0000010000000, 0x0000000000000000 +cvt_si2sd_input: + .quad 0x0020000000000001 +half_ulp: + .long 0x33800000 +neg_half_ulp: + .long 0xb3800000 +packed_one: + .long 0x3f800000, 0x00000000, 0x3f800000, 0x00000000 +packed_half: + .long 0x33800000, 0x00000000, 0x00000000, 0x00000000 +one_d: + .quad 0x3ff0000000000000 +negative_one_d: + .quad 0xbff0000000000000 +one_point25_d: + .quad 0x3ff4000000000000 +next_one_d: + .quad 0x3ff0000000000001 +half_ulp_d: + .quad 0x3ca0000000000000 +neg_half_ulp_d: + .quad 0xbca0000000000000 +packed_one_d: + .quad 0x3ff0000000000000, 0x3ff0000000000000 +packed_half_d: + .quad 0x3ca0000000000000, 0x0000000000000000 +packed_neg_half: + .long 0xb3800000, 0x00000000, 0x00000000, 0x00000000 +packed_neg_half_d: + .quad 0xbca0000000000000, 0x0000000000000000 +packed_one_point25: + .long 0x3fa00000, 0x00000000, 0x3fa00000, 0x00000000 +packed_next_one: + .long 0x3f800001, 0x3f800000, 0x3f800000, 0x3f800000 +packed_one_point25_d: + .quad 0x3ff4000000000000, 0x3ff4000000000000 +packed_next_one_d: + .quad 0x3ff0000000000001, 0x3ff0000000000000 +div_float_num: + .long 0x3d800000 +div_float_den: + .long 0x3fc80000 +packed_div_float_num: + .long 0x3d800000, 0x3d800000, 0x3d800000, 0x3d800000 +packed_div_float_den: + .long 0x3fc80000, 0x3fc80000, 0x3fc80000, 0x3fc80000 +div_double_num: + .quad 0x3fb0000000000000 +div_double_den: + .quad 0x3fc8000000000000 +packed_div_double_num: + .quad 0x3fb0000000000000, 0x3fb0000000000000 +packed_div_double_den: + .quad 0x3fc8000000000000, 0x3fc8000000000000 +sqrt_two: + .long 0x40000000 +packed_sqrt_two: + .long 0x40000000, 0x40000000, 0x40000000, 0x40000000 +sqrt_three_d: + .quad 0x4008000000000000 +packed_sqrt_three_d: + .quad 0x4008000000000000, 0x4008000000000000 +hadd_src1_s: + .long 0x3f800000, 0x33800000, 0x00000000, 0x00000000 +hsub_src1_s: + .long 0x3f800000, 0xb3800000, 0x00000000, 0x00000000 +packed_zero_s: + .long 0x00000000, 0x00000000, 0x00000000, 0x00000000 +hadd_src1_d: + .quad 0x3ff0000000000000, 0x3ca0000000000000 +hsub_src1_d: + .quad 0x3ff0000000000000, 0xbca0000000000000 +packed_zero_d: + .quad 0x0000000000000000, 0x0000000000000000 +dpps_src1_s: + .long 0x3f800000, 0x3f800000, 0x00000000, 0x00000000 +dpps_src2_s: + .long 0x3f800000, 0x33800000, 0x00000000, 0x00000000 +dppd_src1_d: + .quad 0x3ff0000000000000, 0x3ff0000000000000 +dppd_src2_d: + .quad 0x3ff0000000000000, 0x3ca0000000000000 +fldenv_area: + .long 0x0000077f + .long 0x00000000 + .long 0x0000ffff + .zero 16 + +.section .bss +.align 2 +report: + .zero 8 +.p2align 4 +frstor_area: + .zero 108 +.p2align 6 +xsave_area: + .zero 1024 diff --git a/tests/integration/fpu-flags-guest.S b/tests/integration/fpu-flags-guest.S new file mode 100644 index 00000000000..98cc49f1bce --- /dev/null +++ b/tests/integration/fpu-flags-guest.S @@ -0,0 +1,660 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Freestanding x86_64 guest for x87/SSE exception-state isolation tests. + * + * Each test starts from clean x87 and MXCSR state and reports only the + * architected exception flags. The runner compares the x87 status word and + * MXCSR independently, which makes cross-domain contamination visible. + * Cases 73-80 check only MXCSR and leave the x87 report field zero. + */ + +#ifndef TEST_CASE +#error TEST_CASE must select one FPU flag test +#endif + +.equ __NR_write, 1 +.equ __NR_exit, 60 +.equ X87_STATUS_MASK, 0x003f +.equ MXCSR_STATUS_MASK, 0x0000003f +.equ REPORT_SIZE, 8 + +.section .text +.global _start +.type _start, @function +_start: + fninit + fnclex + ldmxcsr clean_mxcsr(%rip) + +#if TEST_CASE == 1 + /* x87 0/0: Invalid belongs to x87, not MXCSR. */ + fldz + fldz + fdivp %st, %st(1) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 2 + /* SSE 0/0: Invalid belongs to MXCSR, not x87 status. */ + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 3 + /* x87 Invalid must not leak into a later SSE read. */ + fldz + fldz + fdivp %st, %st(1) + movss exact_one(%rip), %xmm0 + addss %xmm0, %xmm0 + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 4 + /* SSE Invalid must not leak into a later x87 read. */ + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 + fld1 + fld1 + faddp %st, %st(1) + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 5 + /* An exact x87 operation must not clear an earlier x87 Invalid. */ + fldz + fldz + fdivp %st, %st(1) + fld1 + fld1 + faddp %st, %st(1) + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 6 + /* An exact SSE operation must not clear an earlier SSE Invalid. */ + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 + movss exact_one(%rip), %xmm0 + addss %xmm0, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 7 + /* x87 1/3: Precision belongs to x87, not MXCSR. */ + fld1 + fdivl inexact_three(%rip) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 8 + /* SSE Invalid and x87 Precision must remain in their own domains. */ + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 + fld1 + fdivl inexact_three(%rip) + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 9 + /* x87 Precision and later SSE Invalid must remain in their own domains. */ + fld1 + fdivl inexact_three(%rip) + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 10 + /* Scalar double 0/0: DIVSD Invalid belongs to MXCSR. */ + xorpd %xmm0, %xmm0 + divsd %xmm0, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 11 + /* Packed single 0/0: DIVPS Invalid belongs to MXCSR. */ + xorps %xmm0, %xmm0 + divps %xmm0, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 12 + /* Packed double 0/0: DIVPD Invalid belongs to MXCSR. */ + xorpd %xmm0, %xmm0 + divpd %xmm0, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 13 + /* Scalar single +Inf + -Inf: ADDSS raises Invalid. */ + movss positive_infinity_s(%rip), %xmm0 + movss negative_infinity_s(%rip), %xmm1 + addss %xmm1, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 14 + /* Scalar double +Inf + -Inf: ADDSD raises Invalid. */ + movsd positive_infinity_d(%rip), %xmm0 + movsd negative_infinity_d(%rip), %xmm1 + addsd %xmm1, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 15 + /* Packed single +Inf + -Inf: ADDPS raises Invalid. */ + movaps packed_positive_infinity_s(%rip), %xmm0 + movaps packed_negative_infinity_s(%rip), %xmm1 + addps %xmm1, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 16 + /* Packed double +Inf + -Inf: ADDPD raises Invalid. */ + movapd packed_positive_infinity_d(%rip), %xmm0 + movapd packed_negative_infinity_d(%rip), %xmm1 + addpd %xmm1, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 17 + /* Scalar single Inf - Inf: SUBSS raises Invalid. */ + movss positive_infinity_s(%rip), %xmm0 + movss positive_infinity_s(%rip), %xmm1 + subss %xmm1, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 18 + /* Scalar double Inf - Inf: SUBSD raises Invalid. */ + movsd positive_infinity_d(%rip), %xmm0 + movsd positive_infinity_d(%rip), %xmm1 + subsd %xmm1, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 19 + /* Packed single Inf - Inf: SUBPS raises Invalid. */ + movaps packed_positive_infinity_s(%rip), %xmm0 + movaps packed_positive_infinity_s(%rip), %xmm1 + subps %xmm1, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 20 + /* Packed double Inf - Inf: SUBPD raises Invalid. */ + movapd packed_positive_infinity_d(%rip), %xmm0 + movapd packed_positive_infinity_d(%rip), %xmm1 + subpd %xmm1, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 21 + /* Scalar single 0 * Inf: MULSS raises Invalid. */ + xorps %xmm0, %xmm0 + movss positive_infinity_s(%rip), %xmm1 + mulss %xmm1, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 22 + /* Scalar double 0 * Inf: MULSD raises Invalid. */ + xorpd %xmm0, %xmm0 + movsd positive_infinity_d(%rip), %xmm1 + mulsd %xmm1, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 23 + /* Packed single 0 * Inf: MULPS raises Invalid. */ + xorps %xmm0, %xmm0 + movaps packed_positive_infinity_s(%rip), %xmm1 + mulps %xmm1, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 24 + /* Packed double 0 * Inf: MULPD raises Invalid. */ + xorpd %xmm0, %xmm0 + movapd packed_positive_infinity_d(%rip), %xmm1 + mulpd %xmm1, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 25 + /* Scalar single sqrt(-1): SQRTSS raises Invalid. */ + movss negative_one_s(%rip), %xmm0 + sqrtss %xmm0, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 26 + /* Scalar double sqrt(-1): SQRTSD raises Invalid. */ + movsd negative_one_d(%rip), %xmm0 + sqrtsd %xmm0, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 27 + /* Packed single sqrt(-1): SQRTPS raises Invalid. */ + movaps packed_negative_one_s(%rip), %xmm0 + sqrtps %xmm0, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 28 + /* Packed double sqrt(-1): SQRTPD raises Invalid. */ + movapd packed_negative_one_d(%rip), %xmm0 + sqrtpd %xmm0, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 29 + /* x87 +Inf + -Inf: FADD raises Invalid in x87 status only. */ + fldl positive_infinity_d(%rip) + faddl negative_infinity_d(%rip) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 30 + /* x87 Inf - Inf: FSUB raises Invalid in x87 status only. */ + fldl positive_infinity_d(%rip) + fsubl positive_infinity_d(%rip) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 31 + /* x87 0 * Inf: FMUL raises Invalid in x87 status only. */ + fldz + fmull positive_infinity_d(%rip) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 32 + /* x87 sqrt(-1): FSQRT raises Invalid in x87 status only. */ + fldl negative_one_d(%rip) + fsqrt + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 33 + /* x87 +Inf + -Inf: FADDP raises Invalid before popping the stack. */ + fldl positive_infinity_d(%rip) + fldl negative_infinity_d(%rip) + faddp %st, %st(1) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 34 + /* x87 Inf - Inf: FSUBP raises Invalid before popping the stack. */ + fldl positive_infinity_d(%rip) + fldl positive_infinity_d(%rip) + /* AT&T fsubrp encodes the architectural FSUBP st(1), st(0). */ + fsubrp %st, %st(1) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 35 + /* x87 0 * Inf: FMULP raises Invalid before popping the stack. */ + fldz + fldl positive_infinity_d(%rip) + fmulp %st, %st(1) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 36 + /* x87 3/10 or 10/3: FDIVP raises Precision before popping. */ + fldl inexact_three(%rip) + fldl inexact_ten(%rip) + /* AT&T fdivrp encodes the architectural FDIVP st(1), st(0). */ + fdivrp %st, %st(1) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 37 + /* x87 +Inf reverse-subtracted from +Inf raises Invalid. */ + fldl positive_infinity_d(%rip) + fsubrl positive_infinity_d(%rip) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 38 + /* Architectural FSUBRP raises Invalid before popping the stack. */ + fldl positive_infinity_d(%rip) + fldl positive_infinity_d(%rip) + /* AT&T fsubp encodes the architectural FSUBRP st(1), st(0). */ + fsubp %st, %st(1) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 39 + /* SSE Invalid and later x87 reverse-divide Precision stay isolated. */ + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 + fldl inexact_ten(%rip) + fdivrl inexact_three(%rip) /* 3.0 / 10.0 */ + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 40 + /* SSE Invalid and later x87 FDIVRP Precision stay isolated. */ + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 + fldl inexact_ten(%rip) + fldl inexact_three(%rip) + /* AT&T fdivp encodes the architectural FDIVRP st(1), st(0). */ + fdivp %st, %st(1) /* 3.0 / 10.0 */ + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 41 + /* x87 integer add: 2^64 + 1 is inexact at extended precision. */ + fldl two_pow_64(%rip) + fiaddl integer_one(%rip) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 42 + /* x87 integer subtract: 2^65 - 1 is inexact at extended precision. */ + fldl two_pow_65(%rip) + fisubl integer_one(%rip) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 43 + /* x87 reverse integer subtract: 1 - 2^65 is inexact. */ + fldl two_pow_65(%rip) + fisubrl integer_one(%rip) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 44 + /* x87 integer multiply: +Inf * 0 raises Invalid. */ + fldl positive_infinity_d(%rip) + fimull integer_zero(%rip) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 45 + /* x87 integer divide: 1.0 / 3 is inexact. */ + fld1 + fidivl integer_three(%rip) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 46 + /* x87 reverse integer divide: 3 / 10.0 is inexact. */ + fldl inexact_ten(%rip) + fidivrl integer_three(%rip) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 47 + /* x87 1/0: FNSTSW must observe Divide-by-Zero from FCSR Flags. */ + fld1 + fdivl positive_zero_d(%rip) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE == 48 + /* FXSAVE must commit x87 Precision into its saved FSW. */ + fld1 + fdivl inexact_three(%rip) + fxsave fxsave_area(%rip) + movw fxsave_area+2(%rip), %ax + movl fxsave_area+24(%rip), %ecx + movl %ecx, report_mxcsr(%rip) +#elif TEST_CASE == 49 + /* FXSAVE must commit SSE Invalid into its saved MXCSR. */ + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 + fxsave fxsave_area(%rip) + movw fxsave_area+2(%rip), %ax + movl fxsave_area+24(%rip), %ecx + movl %ecx, report_mxcsr(%rip) +#elif TEST_CASE == 50 + /* FXSAVE must commit x87 Divide-by-Zero into its saved FSW. */ + fld1 + fdivl positive_zero_d(%rip) + fxsave fxsave_area(%rip) + movw fxsave_area+2(%rip), %ax + movl fxsave_area+24(%rip), %ecx + movl %ecx, report_mxcsr(%rip) +#elif TEST_CASE == 51 + /* FXSAVE must commit x87 Invalid from 0/0 into its saved FSW. */ + fldz + fdivl positive_zero_d(%rip) + fxsave fxsave_area(%rip) + movw fxsave_area+2(%rip), %ax + movl fxsave_area+24(%rip), %ecx + movl %ecx, report_mxcsr(%rip) +#elif TEST_CASE == 52 + /* XSAVE must commit x87 Precision into its saved FSW. */ + fld1 + fdivl inexact_three(%rip) + movl $3, %eax + xorl %edx, %edx + xsave xsave_area(%rip) + movw xsave_area+2(%rip), %ax + movl xsave_area+24(%rip), %ecx + movl %ecx, report_mxcsr(%rip) +#elif TEST_CASE == 53 + /* XSAVE must commit SSE Invalid into its saved MXCSR. */ + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 + movl $3, %eax + xorl %edx, %edx + xsave xsave_area(%rip) + movw xsave_area+2(%rip), %ax + movl xsave_area+24(%rip), %ecx + movl %ecx, report_mxcsr(%rip) +#elif TEST_CASE == 54 + /* XSAVEOPT must commit x87 Precision into its saved FSW. */ + fld1 + fdivl inexact_three(%rip) + movl $3, %eax + xorl %edx, %edx + xsaveopt xsave_area(%rip) + movw xsave_area+2(%rip), %ax + movl xsave_area+24(%rip), %ecx + movl %ecx, report_mxcsr(%rip) +#elif TEST_CASE == 55 + /* XSAVEOPT must commit SSE Invalid into its saved MXCSR. */ + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 + movl $3, %eax + xorl %edx, %edx + xsaveopt xsave_area(%rip) + movw xsave_area+2(%rip), %ax + movl xsave_area+24(%rip), %ecx + movl %ecx, report_mxcsr(%rip) +#elif TEST_CASE == 56 + /* FXRSTOR of clean state must discard pending x87 Precision. */ + fxsave fxsave_area(%rip) + fld1 + fdivl inexact_three(%rip) + fxrstor fxsave_area(%rip) + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 57 + /* FXRSTOR of clean state must discard pending SSE Invalid. */ + fxsave fxsave_area(%rip) + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 + fxrstor fxsave_area(%rip) + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 58 + /* FXRSTOR must expose flags that are present in the restored image. */ + fxsave fxsave_area(%rip) + orw $0x20, fxsave_area+2(%rip) + orl $0x01, fxsave_area+24(%rip) + fxrstor fxsave_area(%rip) + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 59 + /* FNCLEX clears x87 status only; pending SSE Invalid must survive. */ + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 + fnclex + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 60 + /* FNCLEX must clear an x87 Invalid without affecting MXCSR. */ + fldz + fldz + fdivp %st, %st(1) + fnclex + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 61 + /* FNINIT resets x87 only; pending SSE Invalid must survive. */ + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 + fninit + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 62 + /* AVX scalar divide by zero must set MXCSR Divide-by-Zero. */ + movss exact_one(%rip), %xmm0 + xorps %xmm1, %xmm1 + vdivss %xmm1, %xmm0, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 63 + /* AVX scalar sqrt(-1) must set MXCSR Invalid. */ + vmovss negative_one_s(%rip), %xmm0 + vsqrtss %xmm0, %xmm0, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 64 + /* AVX packed add of 1 and half ULP must set MXCSR Precision. */ + vmovups packed_one_s(%rip), %xmm0 + vaddps packed_half_ulp_s(%rip), %xmm0, %xmm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 65 + /* CVTTSS2SI must switch from an x87 pending flag to SSE Invalid. */ + fld1 + fdivl inexact_three(%rip) + cvttss2si positive_infinity_s(%rip), %eax + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 66 + /* CVTTSD2SI must switch from an x87 pending flag to SSE Invalid. */ + fld1 + fdivl inexact_three(%rip) + cvttsd2si positive_infinity_d(%rip), %eax + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 67 + /* CVTTPS2DQ must switch from an x87 pending flag to SSE Invalid. */ + fld1 + fdivl inexact_three(%rip) + movups packed_positive_infinity_s(%rip), %xmm0 + cvttps2dq %xmm0, %xmm1 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE == 68 + /* CVTTPD2PI must switch from an x87 pending flag to SSE Invalid. */ + fld1 + fdivl inexact_three(%rip) + movupd packed_positive_infinity_d(%rip), %xmm0 + cvttpd2pi %xmm0, %mm0 + fnstsw %ax + stmxcsr report_mxcsr(%rip) +#elif TEST_CASE >= 69 && TEST_CASE <= 72 + /* Save a clean x87/SSE image before producing pending native flags. + * Do not observe MXCSR between DIVSS and XRSTOR: that would flush the + * pending flag and hide an unconditional-clear bug. */ + movl $3, %eax + xorl %edx, %edx + xsave xsave_area(%rip) + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 +#if TEST_CASE == 69 + /* x87-only restore must preserve SSE Invalid. */ + movl $1, %eax +#elif TEST_CASE == 70 + /* SSE-only restore must replace pending Invalid with clean MXCSR. */ + movl $2, %eax +#elif TEST_CASE == 71 + /* Restoring both components must also replace pending Invalid. */ + movl $3, %eax +#else + /* A zero request mask must leave SSE Invalid unchanged. */ + xorl %eax, %eax +#endif + xorl %edx, %edx + xrstor xsave_area(%rip) + stmxcsr report_mxcsr(%rip) + fnstsw %ax +#elif TEST_CASE >= 73 && TEST_CASE <= 80 + /* + * Host-libm x87 helpers must neither introduce SSE flags nor discard + * pending SSE Invalid. Do not read MXCSR before the helper: an early + * read would flush pending flags and hide an unconditional-clear bug. + * Input 1.0 exercises nontrivial sin/cos/tan evaluation, not the zero + * shortcut. All x87 exceptions are masked by the initial FNINIT. + */ +#if TEST_CASE >= 77 + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 +#endif + fld1 +#if TEST_CASE == 73 || TEST_CASE == 77 + fsin +#elif TEST_CASE == 74 || TEST_CASE == 78 + fcos +#elif TEST_CASE == 75 || TEST_CASE == 79 + fptan +#else + fsincos +#endif + stmxcsr report_mxcsr(%rip) + /* x87 transcendental exception accuracy is outside this assertion. + * Zero the report field, not the guest x87 status or native FCSR. */ + xorl %eax, %eax +#else +#error unsupported TEST_CASE +#endif + + andw $X87_STATUS_MASK, %ax + movw %ax, report_x87(%rip) + andl $MXCSR_STATUS_MASK, report_mxcsr(%rip) + + /* write(1, &report_x87, REPORT_SIZE) */ + movl $__NR_write, %eax + movl $1, %edi + leaq report_x87(%rip), %rsi + movl $REPORT_SIZE, %edx + syscall + + xorl %edi, %edi + movl $__NR_exit, %eax + syscall +.size _start, .-_start + +.section .rodata +.align 16 +clean_mxcsr: + .long 0x00001f80 +exact_one: + .long 0x3f800000 +inexact_three: + .quad 0x4008000000000000 +inexact_ten: + .quad 0x4024000000000000 +positive_infinity_s: + .long 0x7f800000 +negative_infinity_s: + .long 0xff800000 +positive_infinity_d: + .quad 0x7ff0000000000000 +negative_infinity_d: + .quad 0xfff0000000000000 +negative_one_s: + .long 0xbf800000 +negative_one_d: + .quad 0xbff0000000000000 +positive_zero_d: + .quad 0x0000000000000000 +two_pow_64: + .quad 0x43f0000000000000 +two_pow_65: + .quad 0x4400000000000000 +integer_zero: + .long 0 +integer_one: + .long 1 +integer_three: + .long 3 + +.p2align 4 +packed_positive_infinity_s: + .long 0x7f800000, 0x7f800000, 0x7f800000, 0x7f800000 +packed_negative_infinity_s: + .long 0xff800000, 0xff800000, 0xff800000, 0xff800000 +packed_positive_infinity_d: + .quad 0x7ff0000000000000, 0x7ff0000000000000 +packed_negative_infinity_d: + .quad 0xfff0000000000000, 0xfff0000000000000 +packed_negative_one_s: + .long 0xbf800000, 0xbf800000, 0xbf800000, 0xbf800000 +packed_negative_one_d: + .quad 0xbff0000000000000, 0xbff0000000000000 +packed_one_s: + .long 0x3f800000, 0x3f800000, 0x3f800000, 0x3f800000 +packed_half_ulp_s: + .long 0x33800000, 0x00000000, 0x00000000, 0x00000000 + +.section .bss +.align 4 +report_x87: + .zero 2 +.zero 2 +report_mxcsr: + .zero 4 +.p2align 4 +fxsave_area: + .zero 512 +.p2align 6 +xsave_area: + .zero 4096 + +.section .note.GNU-stack,"",@progbits diff --git a/tests/integration/fpu-signal-guest.S b/tests/integration/fpu-signal-guest.S new file mode 100644 index 00000000000..ae1dd36c1e2 --- /dev/null +++ b/tests/integration/fpu-signal-guest.S @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Freestanding x86_64 guest for signal-frame observation and sigreturn. + * Self-signalling through kill exercises delivery at a syscall boundary, + * not arbitrary interruption of translated code or a host libm helper. + * Report: uint32_t handler_seen followed by a case-specific uint32_t value. + */ + +#ifndef TEST_CASE +#error TEST_CASE must select a signal FPU test +#endif +#if TEST_CASE < 1 || TEST_CASE > 3 +#error unsupported TEST_CASE +#endif + +.equ __NR_write, 1 +.equ __NR_rt_sigaction, 13 +.equ __NR_rt_sigreturn, 15 +.equ __NR_getpid, 39 +.equ __NR_kill, 62 +.equ __NR_exit, 60 +.equ SIGUSR1, 10 +.equ SA_SIGINFO, 4 +.equ SA_RESTORER, 0x04000000 +.equ MXCSR_STATUS_MASK, 0x0000003f +.equ REPORT_SIZE, 8 + +/* struct target_ucontext.tuc_mcontext is target_sigcontext_64; its + * fpstate pointer lives at offset 184, and tuc_mcontext starts at 40. */ +.equ UC_MCONTEXT_FPSTATE_OFF, 224 +/* struct target_fpstate_fxsave.mxcsr is at offset 24. */ +.equ FPSTATE_MXCSR_OFF, 24 + +.section .text +.global _start +.type _start, @function +_start: + fninit + fnclex + ldmxcsr clean_mxcsr(%rip) + + /* rt_sigaction(SIGUSR1, &act, NULL, 8) */ + leaq sig_handler(%rip), %rax + movq %rax, act_handler(%rip) + movq $(SA_SIGINFO | SA_RESTORER), act_flags(%rip) + leaq sig_restorer(%rip), %rax + movq %rax, act_restorer(%rip) + movl $__NR_rt_sigaction, %eax + movl $SIGUSR1, %edi + leaq act(%rip), %rsi + xorl %edx, %edx + movl $8, %r10d + syscall + testq %rax, %rax + js syscall_failed + + /* Masked SSE 0/0: sets IE sticky in the native FCSR, no trap. */ + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 + + /* kill(getpid(), SIGUSR1). The syscall forces a TB exit, saving the + * native FCSR into env->fcsr, but env->mxcsr is still clean. */ + movl $__NR_getpid, %eax + syscall + testq %rax, %rax + js syscall_failed + movq %rax, %rdi + movl $__NR_kill, %eax + movl $SIGUSR1, %esi + syscall + testq %rax, %rax + js syscall_failed + +#if TEST_CASE == 2 + /* The frame requests ZE only; handler-generated IE must not survive. */ + stmxcsr report_value(%rip) + andl $MXCSR_STATUS_MASK, report_value(%rip) +#elif TEST_CASE == 3 + /* The frame requests RU, while the handler last used RD. */ + movss one(%rip), %xmm0 + addss half_ulp(%rip), %xmm0 + movss %xmm0, report_value(%rip) +#endif + + /* write(1, &handler_seen, REPORT_SIZE) */ + movl $__NR_write, %eax + movl $1, %edi + leaq handler_seen(%rip), %rsi + movl $REPORT_SIZE, %edx + syscall + cmpq $REPORT_SIZE, %rax + jne syscall_failed + + xorl %edi, %edi + movl $__NR_exit, %eax + syscall + +syscall_failed: + movl $1, %edi + movl $__NR_exit, %eax + syscall +.size _start, .-_start + +/* + * handler(int signo, siginfo_t *info, void *ucontext) + * + * Observe MXCSR through the ucontext's fpstate, the same path a real + * handler uses. This bypasses the STMXCSR translator merge, so only a + * signal-frame FCSR flush can make the pending IE visible here. + */ +.type sig_handler, @function +sig_handler: + movl $1, handler_seen(%rip) + movq UC_MCONTEXT_FPSTATE_OFF(%rdx), %rax + testq %rax, %rax + jz syscall_failed +#if TEST_CASE == 1 + movl FPSTATE_MXCSR_OFF(%rax), %ecx + andl $MXCSR_STATUS_MASK, %ecx + movl %ecx, report_value(%rip) +#elif TEST_CASE == 2 + /* Saved frame and live handler state are deliberately different. */ + movl $0x00001f84, FPSTATE_MXCSR_OFF(%rax) + ldmxcsr clean_mxcsr(%rip) + xorps %xmm0, %xmm0 + divss %xmm0, %xmm0 +#else + movl $0x00005f80, FPSTATE_MXCSR_OFF(%rax) + ldmxcsr down_mxcsr(%rip) + movss one(%rip), %xmm0 + addss half_ulp(%rip), %xmm0 +#endif + ret +.size sig_handler, .-sig_handler + +.type sig_restorer, @function +sig_restorer: + movl $__NR_rt_sigreturn, %eax + syscall + ud2 +.size sig_restorer, .-sig_restorer + +.section .rodata +.align 4 +clean_mxcsr: + .long 0x00001f80 +down_mxcsr: + .long 0x00003f80 +one: + .long 0x3f800000 +half_ulp: + .long 0x33800000 + +.section .data +.align 8 +act: +act_handler: + .quad 0 +act_flags: + .quad 0 +act_restorer: + .quad 0 +act_mask: + .quad 0 + +.section .bss +.align 4 +handler_seen: + .zero 4 +report_value: + .zero 4 + +.section .note.GNU-stack,"",@progbits diff --git a/tests/integration/meson.build b/tests/integration/meson.build index 7e86f5652de..67186f54a10 100644 --- a/tests/integration/meson.build +++ b/tests/integration/meson.build @@ -3,6 +3,7 @@ latx_integration_tests = [] if host_machine.cpu_family() == 'loongarch64' subdir('registrations/x11-kzt') subdir('registrations/sandbox') + subdir('registrations/fpu') subdir('registrations/process') subdir('registrations/namespace') subdir('registrations/process-vm') diff --git a/tests/integration/mxcsr-cvt-guest.S b/tests/integration/mxcsr-cvt-guest.S new file mode 100644 index 00000000000..06670bae511 --- /dev/null +++ b/tests/integration/mxcsr-cvt-guest.S @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Freestanding x86_64 guest for CVT/CVTT exception-state tests. + * + * Build this source once per TEST_CASE. It writes a fixed-size binary report + * to stdout so that the host runner, rather than translated guest branches, + * decides whether the observed values are correct. + */ + +#ifndef TEST_CASE +#error TEST_CASE must select one CVT/CVTT test +#endif + +.equ __NR_write, 1 +.equ __NR_exit, 60 +.equ MXCSR_STATUS_MASK, 0x3f +.equ REPORT_SIZE, 20 + +.section .text +.global _start +.type _start, @function +_start: + /* Do not use a guest fenv function as the test fixture. */ + fnclex + ldmxcsr clean_mxcsr(%rip) + +#if TEST_CASE == 1 + /* Exact CVTT: result 7, no exception status. */ + cvttss2si exact_seven(%rip), %eax + movl %eax, report_results(%rip) +#elif TEST_CASE == 2 + /* Positive infinity: x86 indefinite integer and Invalid. */ + cvttss2si positive_infinity(%rip), %eax + movl %eax, report_results(%rip) +#elif TEST_CASE == 3 + /* Rounded CVT of qNaN: x86 indefinite integer and Invalid. */ + cvtss2si quiet_nan(%rip), %eax + movl %eax, report_results(%rip) +#elif TEST_CASE == 4 + /* Invalid in lane 0; the remaining lanes are exact. */ + movups packed_lane0_invalid(%rip), %xmm0 + cvttps2dq %xmm0, %xmm1 + movdqu %xmm1, report_results(%rip) +#elif TEST_CASE == 5 + /* A later exact conversion must not erase the earlier Invalid flag. */ + cvttss2si positive_infinity(%rip), %eax + movl %eax, report_results(%rip) + cvttss2si exact_seven(%rip), %eax + movl %eax, report_results+4(%rip) +#elif TEST_CASE == 6 + /* Truncating 1.5: result 1 and Precision. */ + cvttss2si inexact_one_point_five(%rip), %eax + movl %eax, report_results(%rip) +#else +#error unsupported TEST_CASE +#endif + + /* Observe immediately after the producer sequence in the same TB. */ + stmxcsr report_status(%rip) + andl $MXCSR_STATUS_MASK, report_status(%rip) + + /* write(1, &report_status, REPORT_SIZE) */ + movl $__NR_write, %eax + movl $1, %edi + leaq report_status(%rip), %rsi + movl $REPORT_SIZE, %edx + syscall + + xorl %edi, %edi + movl $__NR_exit, %eax + syscall +.size _start, .-_start + +.section .rodata +.align 16 +clean_mxcsr: + .long 0x00001f80 +exact_seven: + .long 0x40e00000 +positive_infinity: + .long 0x7f800000 +quiet_nan: + .long 0x7fc00000 +inexact_one_point_five: + .long 0x3fc00000 + +.align 16 +packed_lane0_invalid: + .long 0x7fc00000 + .long 0x3f800000 + .long 0x40000000 + .long 0x40400000 + +.section .bss +.align 16 +/* Five little-endian uint32_t values: status followed by four results. */ +report_status: + .zero 4 +report_results: + .zero 16 + +.section .note.GNU-stack,"",@progbits diff --git a/tests/integration/registrations/fpu/meson.build b/tests/integration/registrations/fpu/meson.build new file mode 100644 index 00000000000..e10a0a4f48e --- /dev/null +++ b/tests/integration/registrations/fpu/meson.build @@ -0,0 +1,58 @@ +if 'x86_64-linux-user' in target_dirs + # O3 enables CONFIG_LATX_AVX_OPT through optimize-config.h. + fpu_extended_env = {} + if 'CONFIG_LATX_AVX_OPT' in config_host or 'CONFIG_LATX_O3' in config_host + fpu_extended_env = { + 'LATX_TEST_XSAVE': '1', + 'LATX_TEST_AVX': '1', + } + endif + + latx_integration_tests += [{ + 'name': 'test-mxcsr-cvt', + 'runner': find_program('../../test-mxcsr-cvt.sh'), + 'args': [ + emulators['latx-x86_64'], + files('../../mxcsr-cvt-guest.S'), + ], + 'timeout': 120, + }] + latx_integration_tests += [{ + 'name': 'test-fpu-flags', + 'runner': find_program('../../test-fpu-flags.sh'), + 'args': [ + emulators['latx-x86_64'], + files('../../fpu-flags-guest.S'), + ], + 'env': fpu_extended_env, + 'timeout': 120, + }] + latx_integration_tests += [{ + 'name': 'test-fpu-controls', + 'runner': find_program('../../test-fpu-controls.sh'), + 'args': [ + emulators['latx-x86_64'], + files('../../fpu-controls-guest.S'), + ], + 'env': fpu_extended_env, + 'timeout': 120, + }] + latx_integration_tests += [{ + 'name': 'test-x87-pop', + 'runner': find_program('../../test-x87-pop.sh'), + 'args': [ + emulators['latx-x86_64'], + files('../../x87-pop-guest.S'), + ], + 'timeout': 120, + }] + latx_integration_tests += [{ + 'name': 'test-fpu-signal', + 'runner': find_program('../../test-fpu-signal.sh'), + 'args': [ + emulators['latx-x86_64'], + files('../../fpu-signal-guest.S'), + ], + 'timeout': 120, + }] +endif diff --git a/tests/integration/registrations/meson.build b/tests/integration/registrations/meson.build index ddccb9e990f..4506d4b5eb9 100644 --- a/tests/integration/registrations/meson.build +++ b/tests/integration/registrations/meson.build @@ -4,6 +4,7 @@ foreach test_def : latx_integration_tests test_def['name'], test_def['runner'], args: test_def['args'], + env: test_def.get('env', {}), protocol: 'exitcode', suite: 'latx-integration', timeout: test_def.get('timeout', 30), diff --git a/tests/integration/test-fpu-controls.sh b/tests/integration/test-fpu-controls.sh new file mode 100755 index 00000000000..5b2987a9463 --- /dev/null +++ b/tests/integration/test-fpu-controls.sh @@ -0,0 +1,174 @@ +#!/bin/sh +set -eu + +emulator=$1 +source_file=$2 +softfpu=${LATX_SOFTFPU:-1} +workdir=$(mktemp -d) +failures=0 +trap 'rm -rf "$workdir"' EXIT HUP INT TERM + +if 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 + +run_case() +{ + case_id=$1 + case_name=$2 + expected=$3 + expected_size=${4:-4} + rounding_opt=${5:-0} + guest="$workdir/fpu-controls-$case_id" + report="$workdir/report-$case_id.bin" + + "$clang" --target=x86_64-linux-gnu -fuse-ld=lld -nostdlib -static \ + -Wl,--build-id=none -DTEST_CASE="$case_id" \ + "$source_file" -o "$guest" + + set +e + timeout -s KILL 10 env LATX_AOT=0 LATX_KZT=0 LATX_SOFTFPU="$softfpu" \ + LATX_ROUNDING_OPT="$rounding_opt" \ + "$emulator" "$guest" >"$report" + ret=$? + set -e + + case $ret in + 0) + ;; + 124) + echo "FAIL: $case_name timed out" >&2 + failures=$((failures + 1)) + return 0 + ;; + *) + echo "FAIL: $case_name returned $ret" >&2 + failures=$((failures + 1)) + return 0 + ;; + esac + + report_size=$(wc -c <"$report" | tr -d '[:space:]') + if [ "$report_size" -ne "$expected_size" ]; then + echo "FAIL: $case_name wrote $report_size report bytes, expected $expected_size" >&2 + failures=$((failures + 1)) + return 0 + fi + + actual=$(od -An -v -tx1 "$report" | tr -d '[:space:]') + if [ "$actual" != "$expected" ]; then + echo "FAIL: $case_name produced the wrong result" >&2 + echo " expected: $expected" >&2 + echo " actual: $actual" >&2 + failures=$((failures + 1)) + return 0 + fi + + echo "PASS: $case_name" +} + +run_case 1 fninit-preserves-mxcsr-round-up 0100803f +run_case 1 fninit-helper-invalidates-sse-rm-cache 0100803f 4 1 +run_case 2 x87-round-up-after-fldcw 0100803f +run_case 3 fldcw-invalidates-sse-domain 0100803f +run_case 3 fldcw-helper-invalidates-sse-rm-cache 0100803f 4 1 +run_case 4 fldenv-invalidates-sse-domain 0100803f +run_case 4 fldenv-helper-invalidates-sse-rm-cache 0100803f 4 1 +run_case 5 frstor-restores-x87-round-down 0000803f + +run_case 48 haddps-round-up 0100803f +run_case 49 haddpd-round-up 010000000000f03f 8 +run_case 50 hsubps-round-up 0100803f +run_case 51 hsubpd-round-up 010000000000f03f 8 +run_case 52 dpps-round-up 0100803f +run_case 53 dppd-round-up 010000000000f03f 8 +run_case 54 addsubps-round-up 0100803f +run_case 55 addsubpd-round-up 010000000000f03f 8 + +# Cover both the SoftFloat-only and host-fesetround control paths. +run_case 69 fnsave-preserves-sse-round-up 0100803f +run_case 69 fnsave-helper-invalidates-sse-rm-cache 0100803f 4 1 +run_case 70 fsave-preserves-sse-round-up 0100803f +run_case 70 fsave-helper-invalidates-sse-rm-cache 0100803f 4 1 +run_case 71 frstor-preserves-sse-round-up 0100803f +run_case 71 frstor-helper-invalidates-sse-rm-cache 0100803f 4 1 +run_case 56 cvtdq2ps-rounding 0000804b0100804b 8 +run_case 57 cvtsd2ss-rounding 0000803f0100803f 8 +run_case 58 cvtss2si-rounding 0200000001000000 8 +run_case 59 cvttss2si-ignores-rounding 0100000001000000 8 +run_case 60 cvtps2dq-rounding 0200000001000000 8 +run_case 61 cvtpd2dq-rounding 0200000001000000 8 +run_case 62 cvtpd2ps-rounding 0000803f0100803f 8 +run_case 63 cvtsi2ss-rounding 0000804b0100804b 8 + +if [ "${LATX_TEST_XSAVE:-0}" = 1 ]; then + run_case 6 xrstor-restores-x87-round-down 0000803f + run_case 7 xrstor-x87-only-restores-round-down 0000803f + run_case 8 xrstor-sse-only-restores-round-down 0000803f + run_case 9 xrstor-both-restores-round-down 0000803f0000803f 8 +else + echo "SKIP: XRSTOR control case requires LATX_TEST_XSAVE=1" +fi + +if [ "${LATX_TEST_AVX:-0}" = 1 ]; then + run_case 10 vaddss-round-up 0100803f + run_case 11 vaddps-round-up 0100803f + run_case 12 vaddsd-round-up 010000000000f03f 8 + run_case 13 vaddpd-round-up 010000000000f03f 8 + run_case 14 vsubss-round-up 0100803f + run_case 15 vsubps-round-up 0100803f + run_case 16 vsubsd-round-up 010000000000f03f 8 + run_case 17 vsubpd-round-up 010000000000f03f 8 + run_case 18 vmulss-round-up 0200a03f + run_case 19 vmulps-round-up 0200a03f + run_case 20 vmulsd-round-up 020000000000f43f 8 + run_case 21 vmulpd-round-up 020000000000f43f 8 + run_case 22 vdivss-round-up 0bd7233d + run_case 23 vdivps-round-up 0bd7233d + run_case 24 vdivsd-round-up 565555555555d53f 8 + run_case 25 vdivpd-round-up 565555555555d53f 8 + run_case 26 vsqrtss-round-up f404b53f + run_case 27 vsqrtps-round-up f404b53f + run_case 28 vsqrtsd-round-up ab4c58e87ab6fb3f 8 + run_case 29 vsqrtpd-round-up ab4c58e87ab6fb3f 8 + run_case 30 vaddsubps-round-up 0100803f + run_case 31 vaddsubpd-round-up 010000000000f03f 8 + run_case 32 vhaddps-round-up 0100803f + run_case 33 vhaddpd-round-up 010000000000f03f 8 + run_case 34 vhsubps-round-up 0100803f + run_case 35 vhsubpd-round-up 010000000000f03f 8 + run_case 36 vfmadd132ss-round-up 0100803f + run_case 37 vfmadd132ps-round-up 0100803f + run_case 38 vfmadd132sd-round-up 010000000000f03f 8 + run_case 39 vfmadd132pd-round-up 010000000000f03f 8 + run_case 40 vfmsub132ss-round-up 0100803f + run_case 41 vfmsub132sd-round-up 010000000000f03f 8 + run_case 42 vfnmadd132ss-round-up 0100803f + run_case 43 vfnmadd132sd-round-up 010000000000f03f 8 + run_case 44 vfnmsub132ss-round-up 0100803f + run_case 45 vfnmsub132sd-round-up 010000000000f03f 8 + run_case 46 vdpps-round-up 0100803f + run_case 47 vdppd-round-up 010000000000f03f 8 +else + echo "SKIP: AVX arithmetic cases require LATX_TEST_AVX=1" +fi + +if [ "${LATX_TEST_AVX:-0}" = 1 ]; then + run_case 64 vcvtpd2ps-rounding 0000803f0100803f 8 + run_case 65 vcvtdq2ps-rounding 0000804b0100804b 8 + run_case 66 vcvtps2dq-rounding 0200000001000000 8 + run_case 67 vcvtpd2dq-rounding 0200000001000000 8 + run_case 68 vcvtsi2sd-rounding 00000000000040430100000000004043 16 +else + echo "SKIP: AVX CVT rounding cases require LATX_TEST_AVX=1" +fi + +if [ "$failures" -ne 0 ]; then + echo "FAIL: $failures FPU control test cases failed" >&2 + exit 1 +fi diff --git a/tests/integration/test-fpu-flags.sh b/tests/integration/test-fpu-flags.sh new file mode 100755 index 00000000000..30d15a65b7e --- /dev/null +++ b/tests/integration/test-fpu-flags.sh @@ -0,0 +1,260 @@ +#!/bin/sh +set -eu + +emulator=$1 +source_file=$2 +softfpu=${LATX_SOFTFPU:-1} +workdir=$(mktemp -d) +failures=0 +trap 'rm -rf "$workdir"' EXIT HUP INT TERM + +if 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 + +if ! command -v od >/dev/null 2>&1; then + echo "SKIP: od is required to inspect the guest report" + exit 77 +fi + +run_case() +{ + case_id=$1 + case_name=$2 + expected_report=$3 + guest="$workdir/fpu-flags-$case_id" + + "$clang" --target=x86_64-linux-gnu -fuse-ld=lld -nostdlib -static \ + -Wl,--build-id=none -DTEST_CASE="$case_id" \ + "$source_file" -o "$guest" + + report="$workdir/report-$case_id.bin" + + set +e + timeout -s KILL 10 env LATX_AOT=0 LATX_KZT=0 LATX_SOFTFPU="$softfpu" \ + LATX_ROUNDING_OPT=0 \ + "$emulator" "$guest" >"$report" + ret=$? + set -e + + case $ret in + 0) + ;; + 124) + echo "FAIL: $case_name timed out" >&2 + failures=$((failures + 1)) + return 0 + ;; + *) + echo "FAIL: $case_name returned $ret" >&2 + failures=$((failures + 1)) + return 0 + ;; + esac + + report_size=$(wc -c <"$report" | tr -d '[:space:]') + if [ "$report_size" -ne 8 ]; then + echo "FAIL: $case_name wrote $report_size report bytes, expected 8" >&2 + failures=$((failures + 1)) + return 0 + fi + + actual_report=$(od -An -v -tx1 "$report" | tr -d '[:space:]') + if [ "$actual_report" != "$expected_report" ]; then + echo "FAIL: $case_name produced the wrong raw report" >&2 + echo " expected: $expected_report" >&2 + echo " actual: $actual_report" >&2 + failures=$((failures + 1)) + return 0 + fi + + echo "PASS: $case_name" +} + +# Reports contain a little-endian uint16_t x87 status field followed by a +# little-endian uint32_t MXCSR status field. Both are masked to exception +# flags only by the guest, so the expected values are stable across runs. +run_case 1 x87-invalid-isolated \ + 0100000000000000 +run_case 2 sse-invalid-isolated \ + 0000000001000000 +run_case 3 x87-invalid-then-sse-exact \ + 0100000000000000 +run_case 4 sse-invalid-then-x87-exact \ + 0000000001000000 +run_case 5 x87-invalid-then-x87-exact-sticky \ + 0100000000000000 +run_case 6 sse-invalid-then-sse-exact-sticky \ + 0000000001000000 +run_case 7 x87-inexact-isolated \ + 2000000000000000 +run_case 8 sse-invalid-then-x87-inexact \ + 2000000001000000 +run_case 9 x87-inexact-then-sse-invalid \ + 2000000001000000 +run_case 10 sse-divsd-invalid \ + 0000000001000000 +run_case 11 sse-divps-invalid \ + 0000000001000000 +run_case 12 sse-divpd-invalid \ + 0000000001000000 +run_case 13 sse-addss-invalid \ + 0000000001000000 +run_case 14 sse-addsd-invalid \ + 0000000001000000 +run_case 15 sse-addps-invalid \ + 0000000001000000 +run_case 16 sse-addpd-invalid \ + 0000000001000000 +run_case 17 sse-subss-invalid \ + 0000000001000000 +run_case 18 sse-subsd-invalid \ + 0000000001000000 +run_case 19 sse-subps-invalid \ + 0000000001000000 +run_case 20 sse-subpd-invalid \ + 0000000001000000 +run_case 21 sse-mulss-invalid \ + 0000000001000000 +run_case 22 sse-mulsd-invalid \ + 0000000001000000 +run_case 23 sse-mulps-invalid \ + 0000000001000000 +run_case 24 sse-mulpd-invalid \ + 0000000001000000 +run_case 25 sse-sqrtss-invalid \ + 0000000001000000 +run_case 26 sse-sqrtsd-invalid \ + 0000000001000000 +run_case 27 sse-sqrtps-invalid \ + 0000000001000000 +run_case 28 sse-sqrtpd-invalid \ + 0000000001000000 +run_case 29 x87-fadd-invalid \ + 0100000000000000 +run_case 30 x87-fsub-invalid \ + 0100000000000000 +run_case 31 x87-fmul-invalid \ + 0100000000000000 +run_case 32 x87-fsqrt-invalid \ + 0100000000000000 +run_case 33 x87-faddp-invalid \ + 0100000000000000 +run_case 34 x87-fsubp-invalid \ + 0100000000000000 +run_case 35 x87-fmulp-invalid \ + 0100000000000000 +run_case 36 x87-fdivp-inexact \ + 2000000000000000 +run_case 37 x87-fsubr-invalid \ + 0100000000000000 +run_case 38 x87-fsubrp-invalid \ + 0100000000000000 +run_case 39 sse-invalid-then-x87-fdivr-inexact \ + 2000000001000000 +run_case 40 sse-invalid-then-x87-fdivrp-inexact \ + 2000000001000000 +run_case 41 x87-fiadd-inexact \ + 2000000000000000 +run_case 42 x87-fisub-inexact \ + 2000000000000000 +run_case 43 x87-fisubr-inexact \ + 2000000000000000 +run_case 44 x87-fimul-invalid \ + 0100000000000000 +run_case 45 x87-fidiv-inexact \ + 2000000000000000 +run_case 46 x87-fidivr-inexact \ + 2000000000000000 +run_case 47 x87-fdiv-zero-fnstsw \ + 0400000000000000 +run_case 48 x87-fdiv-inexact-fxsave \ + 2000000000000000 +run_case 49 sse-invalid-fxsave \ + 0000000001000000 +run_case 50 x87-fdiv-zero-fxsave \ + 0400000000000000 +run_case 51 x87-fdiv-invalid-fxsave \ + 0100000000000000 +run_case 56 x87-dirty-then-clean-fxrstor \ + 0000000000000000 +run_case 57 sse-dirty-then-clean-fxrstor \ + 0000000000000000 +run_case 58 fxrstor-restored-flags-visible \ + 2000000001000000 +run_case 59 sse-invalid-then-fnclex \ + 0000000001000000 +run_case 60 x87-invalid-then-fnclex \ + 0000000000000000 +run_case 61 sse-invalid-then-fninit \ + 0000000001000000 + +if [ "${LATX_TEST_XSAVE:-0}" = 1 ]; then + run_case 52 x87-fdiv-inexact-xsave \ + 2000000000000000 + run_case 53 sse-invalid-xsave \ + 0000000001000000 + run_case 54 x87-fdiv-inexact-xsaveopt \ + 2000000000000000 + run_case 55 sse-invalid-xsaveopt \ + 0000000001000000 + if [ "$softfpu" -eq 1 ]; then + run_case 69 xrstor-x87-only-preserves-sse-invalid \ + 0000000001000000 + run_case 70 xrstor-sse-only-clears-sse-invalid \ + 0000000000000000 + run_case 71 xrstor-both-clears-sse-invalid \ + 0000000000000000 + run_case 72 xrstor-empty-mask-preserves-sse-invalid \ + 0000000001000000 + fi +else + echo "SKIP: XSAVE flag cases require a CONFIG_LATX_AVX_OPT build" +fi + +if [ "${LATX_TEST_AVX:-0}" = 1 ]; then + run_case 62 avx-vdivss-zero \ + 0000000004000000 + run_case 63 avx-vsqrtss-invalid \ + 0000000001000000 + run_case 64 avx-vaddps-inexact \ + 0000000020000000 +else + echo "SKIP: AVX flag cases require a CONFIG_LATX_AVX_OPT build" +fi + +# These cases assert MXCSR only; the guest zeros the x87 report field. +run_case 73 fsin-preserves-clean-mxcsr \ + 0000000000000000 +run_case 74 fcos-preserves-clean-mxcsr \ + 0000000000000000 +run_case 75 fptan-preserves-clean-mxcsr \ + 0000000000000000 +run_case 76 fsincos-preserves-clean-mxcsr \ + 0000000000000000 +run_case 77 sse-invalid-then-fsin \ + 0000000001000000 +run_case 78 sse-invalid-then-fcos \ + 0000000001000000 +run_case 79 sse-invalid-then-fptan \ + 0000000001000000 +run_case 80 sse-invalid-then-fsincos \ + 0000000001000000 +run_case 65 x87-then-cvttss2si-invalid \ + 2000000001000000 +run_case 66 x87-then-cvttsd2si-invalid \ + 2000000001000000 +run_case 67 x87-then-cvttps2dq-invalid \ + 2000000001000000 +run_case 68 x87-then-cvttpd2pi-invalid \ + 2000000001000000 + +if [ "$failures" -ne 0 ]; then + echo "FAIL: $failures FPU flag test cases failed" >&2 + exit 1 +fi diff --git a/tests/integration/test-fpu-signal.sh b/tests/integration/test-fpu-signal.sh new file mode 100755 index 00000000000..630b05dfd10 --- /dev/null +++ b/tests/integration/test-fpu-signal.sh @@ -0,0 +1,76 @@ +#!/bin/sh +set -eu + +emulator=$1 +source_file=$2 +softfpu=${LATX_SOFTFPU:-1} + +workdir=$(mktemp -d) +trap 'rm -rf "$workdir"' EXIT HUP INT TERM +failures=0 + +if 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 + +if ! command -v od >/dev/null 2>&1; then + echo "SKIP: od is required to inspect the guest report" + exit 77 +fi + +run_case() +{ + case_id=$1 + case_name=$2 + expected=$3 + guest="$workdir/fpu-signal-$case_id" + report="$workdir/report-$case_id.bin" + + "$clang" --target=x86_64-linux-gnu -fuse-ld=lld -nostdlib -static \ + -Wl,--build-id=none -DTEST_CASE="$case_id" \ + "$source_file" -o "$guest" + + set +e + timeout -s KILL 10 env LATX_AOT=0 LATX_KZT=0 \ + LATX_SOFTFPU="$softfpu" \ + LATX_ROUNDING_OPT=0 "$emulator" "$guest" >"$report" + ret=$? + set -e + if [ "$ret" -ne 0 ]; then + echo "FAIL: $case_name returned $ret (124/137 may indicate timeout)" >&2 + failures=$((failures + 1)) + return 0 + fi + + report_size=$(wc -c <"$report" | tr -d '[:space:]') + if [ "$report_size" -ne 8 ]; then + echo "FAIL: $case_name wrote $report_size bytes, expected 8" >&2 + failures=$((failures + 1)) + return 0 + fi + actual=$(od -An -v -tx1 "$report" | tr -d '[:space:]') + if [ "$actual" != "$expected" ]; then + echo "FAIL: $case_name produced the wrong raw report" >&2 + echo " expected: $expected" >&2 + echo " actual: $actual" >&2 + failures=$((failures + 1)) + return 0 + fi + echo "PASS: $case_name" +} + +# First word proves handler execution; second is frame flags, restored flags, +# or the SSE result after sigreturn. Cases run in separate guest processes. +run_case 1 signal-frame-sees-sse-invalid 0100000001000000 +run_case 2 sigreturn-restores-edited-mxcsr-flags 0100000004000000 +run_case 3 sigreturn-restores-edited-mxcsr-round-up 010000000100803f + +if [ "$failures" -ne 0 ]; then + echo "FAIL: $failures signal FPU cases failed" >&2 + exit 1 +fi diff --git a/tests/integration/test-mxcsr-cvt.sh b/tests/integration/test-mxcsr-cvt.sh new file mode 100755 index 00000000000..cd29d15e92a --- /dev/null +++ b/tests/integration/test-mxcsr-cvt.sh @@ -0,0 +1,99 @@ +#!/bin/sh +set -eu + +emulator=$1 +source_file=$2 +softfpu=${LATX_SOFTFPU:-1} +workdir=$(mktemp -d) +failures=0 +trap 'rm -rf "$workdir"' EXIT HUP INT TERM + +if 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 + +if ! command -v od >/dev/null 2>&1; then + echo "SKIP: od is required to inspect the guest report" + exit 77 +fi + +run_case() +{ + case_id=$1 + case_name=$2 + expected_report=$3 + guest="$workdir/mxcsr-cvt-$case_id" + + "$clang" --target=x86_64-linux-gnu -fuse-ld=lld -nostdlib -static \ + -Wl,--build-id=none -DTEST_CASE="$case_id" \ + "$source_file" -o "$guest" + + for cvt_opt in 0 1; do + report="$workdir/report-$case_id-$cvt_opt.bin" + + set +e + timeout -s KILL 10 env LATX_AOT=0 LATX_KZT=0 \ + LATX_SOFTFPU="$softfpu" LATX_ROUNDING_OPT=0 LATX_CVT_OPT="$cvt_opt" \ + "$emulator" "$guest" >"$report" + ret=$? + set -e + + case $ret in + 0) + ;; + 124) + echo "FAIL: $case_name timed out with LATX_CVT_OPT=$cvt_opt" >&2 + failures=$((failures + 1)) + continue + ;; + *) + echo "FAIL: $case_name returned $ret with LATX_CVT_OPT=$cvt_opt" >&2 + failures=$((failures + 1)) + continue + ;; + esac + + report_size=$(wc -c <"$report" | tr -d '[:space:]') + if [ "$report_size" -ne 20 ]; then + echo "FAIL: $case_name wrote $report_size report bytes, expected 20, with LATX_CVT_OPT=$cvt_opt" >&2 + failures=$((failures + 1)) + continue + fi + + actual_report=$(od -An -v -tx1 "$report" | tr -d '[:space:]') + if [ "$actual_report" != "$expected_report" ]; then + echo "FAIL: $case_name produced the wrong raw report with LATX_CVT_OPT=$cvt_opt" >&2 + echo " expected: $expected_report" >&2 + echo " actual: $actual_report" >&2 + failures=$((failures + 1)) + continue + fi + + echo "PASS: $case_name with LATX_CVT_OPT=$cvt_opt" + done +} + +# Each expected report is five little-endian uint32_t values encoded as bytes: +# MXCSR status bits followed by up to four conversion results. +run_case 1 exact-normal \ + 0000000007000000000000000000000000000000 +run_case 2 scalar-cvtt-invalid \ + 0100000000000080000000000000000000000000 +run_case 3 scalar-cvt-invalid \ + 0100000000000080000000000000000000000000 +run_case 4 packed-lane0-invalid \ + 0100000000000080010000000200000003000000 +run_case 5 invalid-then-exact-sticky \ + 0100000000000080070000000000000000000000 +run_case 6 scalar-inexact \ + 2000000001000000000000000000000000000000 + +if [ "$failures" -ne 0 ]; then + echo "FAIL: $failures MXCSR/CVT test variants failed" >&2 + exit 1 +fi diff --git a/tests/integration/test-x87-pop.sh b/tests/integration/test-x87-pop.sh new file mode 100755 index 00000000000..7b3b581ff62 --- /dev/null +++ b/tests/integration/test-x87-pop.sh @@ -0,0 +1,97 @@ +#!/bin/sh +set -eu + +emulator=$1 +source_file=$2 +softfpu=${LATX_SOFTFPU:-1} +workdir=$(mktemp -d) +failures=0 +trap 'rm -rf "$workdir"' EXIT HUP INT TERM + +if 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 + +if ! command -v od >/dev/null 2>&1; then + echo "SKIP: od is required to inspect the guest report" + exit 77 +fi + +run_case() +{ + case_id=$1 + case_name=$2 + expected_report=$3 + guest="$workdir/x87-pop-$case_id" + + "$clang" --target=x86_64-linux-gnu -fuse-ld=lld -nostdlib -static \ + -Wl,--build-id=none -DTEST_CASE="$case_id" \ + "$source_file" -o "$guest" + + report="$workdir/report-$case_id.bin" + + set +e + timeout -s KILL 10 env LATX_AOT=0 LATX_KZT=0 LATX_SOFTFPU="$softfpu" \ + LATX_ROUNDING_OPT=0 \ + "$emulator" "$guest" >"$report" + ret=$? + set -e + + case $ret in + 0) + ;; + 124) + echo "FAIL: $case_name timed out" >&2 + failures=$((failures + 1)) + return 0 + ;; + *) + echo "FAIL: $case_name returned $ret" >&2 + failures=$((failures + 1)) + return 0 + ;; + esac + + report_size=$(wc -c <"$report" | tr -d '[:space:]') + if [ "$report_size" -ne 16 ]; then + echo "FAIL: $case_name wrote $report_size report bytes, expected 16" >&2 + failures=$((failures + 1)) + return 0 + fi + + actual_report=$(od -An -v -tx1 "$report" | tr -d '[:space:]') + if [ "$actual_report" != "$expected_report" ]; then + echo "FAIL: $case_name produced the wrong raw report" >&2 + echo " expected: $expected_report" >&2 + echo " actual: $actual_report" >&2 + failures=$((failures + 1)) + return 0 + fi + + echo "PASS: $case_name" +} + +# Reports contain the arithmetic result followed by the surviving stack +# sentinel, both as little-endian IEEE-754 binary64 values. +run_case 1 x87-faddp-result-and-pop \ + 00000000000018400000000000002240 +run_case 2 x87-fmulp-result-and-pop \ + 00000000000020400000000000002240 +run_case 3 x87-fsubp-result-direction-and-pop \ + 00000000000000c00000000000002240 +run_case 4 x87-fdivp-result-direction-and-pop \ + 000000000000e03f0000000000002240 +run_case 5 x87-fsubrp-result-direction-and-pop \ + 00000000000000400000000000002240 +run_case 6 x87-fdivrp-result-direction-and-pop \ + 00000000000000400000000000002240 + +if [ "$failures" -ne 0 ]; then + echo "FAIL: $failures x87 pop test cases failed" >&2 + exit 1 +fi diff --git a/tests/integration/x87-pop-guest.S b/tests/integration/x87-pop-guest.S new file mode 100644 index 00000000000..adb61fd4169 --- /dev/null +++ b/tests/integration/x87-pop-guest.S @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Freestanding x86_64 guest for x87 arithmetic-and-pop semantics. + * + * Each case leaves the arithmetic result in ST(0) and a sentinel in ST(1), + * then stores both values. This checks both the destination register and + * that the arithmetic instruction pops the x87 stack exactly once. + */ + +#ifndef TEST_CASE +#error TEST_CASE must select one x87 pop test +#endif + +.equ __NR_write, 1 +.equ __NR_exit, 60 +.equ REPORT_SIZE, 16 + +.section .text +.global _start +.type _start, @function +_start: + fninit + + /* Build ST(0)=right, ST(1)=left, ST(2)=sentinel. */ + fldl sentinel(%rip) + fldl left(%rip) + fldl right(%rip) + +#if TEST_CASE == 1 + /* ST(1) = 2.0 + 4.0, then pop: ST(0)=6.0, ST(1)=9.0. */ + faddp %st, %st(1) +#elif TEST_CASE == 2 + /* ST(1) = 2.0 * 4.0, then pop: ST(0)=8.0, ST(1)=9.0. */ + fmulp %st, %st(1) +#elif TEST_CASE == 3 + /* ST(1) = 2.0 - 4.0, then pop: ST(0)=-2.0, ST(1)=9.0. */ + /* AT&T fsubrp encodes the architectural FSUBP st(1), st(0). */ + fsubrp %st, %st(1) +#elif TEST_CASE == 4 + /* ST(1) = 2.0 / 4.0, then pop: ST(0)=0.5, ST(1)=9.0. */ + /* AT&T fdivrp encodes the architectural FDIVP st(1), st(0). */ + fdivrp %st, %st(1) +#elif TEST_CASE == 5 + /* ST(1) = 4.0 - 2.0, then pop: ST(0)=2.0, ST(1)=9.0. */ + /* AT&T fsubp encodes the architectural FSUBRP st(1), st(0). */ + fsubp %st, %st(1) +#elif TEST_CASE == 6 + /* ST(1) = 4.0 / 2.0, then pop: ST(0)=2.0, ST(1)=9.0. */ + /* AT&T fdivp encodes the architectural FDIVRP st(1), st(0). */ + fdivp %st, %st(1) +#else +#error unsupported TEST_CASE +#endif + + fstpl report_first(%rip) + fstpl report_second(%rip) + + /* write(1, &report_first, REPORT_SIZE) */ + movl $__NR_write, %eax + movl $1, %edi + leaq report_first(%rip), %rsi + movl $REPORT_SIZE, %edx + syscall + + xorl %edi, %edi + movl $__NR_exit, %eax + syscall +.size _start, .-_start + +.section .rodata +.align 8 +sentinel: + .quad 0x4022000000000000 /* 9.0 */ +left: + .quad 0x4000000000000000 /* 2.0 */ +right: + .quad 0x4010000000000000 /* 4.0 */ + +.section .bss +.align 8 +report_first: + .zero 8 +report_second: + .zero 8 + +.section .note.GNU-stack,"",@progbits