This patch fixes incorrect results for [xv]fnm{add,sub}.{s,d}
instructions when rounding toward zero, postive, negative.

According to the LoongArch ISA specification, these instructions perform
a fused multiply-add followed by a negation of the final result.

Previously, the sign inversion was applied before fused operation, which
interfered with rounding decisions that depend on the result sign --
leading to deviations from the expected behavior. This patch corrects
the implementation by applying the negation after fused multiply-add,
ensuring that rounding is performed on the correct intermediate result.

Reported-by: mengqinggang <mengqingg...@loongson.cn>
Signed-off-by: WANG Rui <wang...@loongson.cn>
---
 target/loongarch/tcg/fpu_helper.c     |  8 ++++++++
 target/loongarch/tcg/vec_helper.c     |  7 ++++++-
 tests/tcg/loongarch64/Makefile.target |  2 ++
 tests/tcg/loongarch64/test_fnmsub.c   | 20 ++++++++++++++++++++
 tests/tcg/loongarch64/test_vfnmsub.c  | 27 +++++++++++++++++++++++++++
 5 files changed, 63 insertions(+), 1 deletion(-)
 create mode 100644 tests/tcg/loongarch64/test_fnmsub.c
 create mode 100644 tests/tcg/loongarch64/test_vfnmsub.c

diff --git a/target/loongarch/tcg/fpu_helper.c 
b/target/loongarch/tcg/fpu_helper.c
index fc3fd0561e..970b88ac56 100644
--- a/target/loongarch/tcg/fpu_helper.c
+++ b/target/loongarch/tcg/fpu_helper.c
@@ -389,9 +389,13 @@ uint64_t helper_fmuladd_s(CPULoongArchState *env, uint64_t 
fj,
                           uint64_t fk, uint64_t fa, uint32_t flag)
 {
     uint64_t fd;
+    uint32_t neg_res;
 
+    neg_res = flag & float_muladd_negate_result;
+    flag ^= neg_res;
     fd = nanbox_s(float32_muladd((uint32_t)fj, (uint32_t)fk,
                                  (uint32_t)fa, flag, &env->fp_status));
+    fd |= neg_res ? 0x80000000ULL : 0;
     update_fcsr0(env, GETPC());
     return fd;
 }
@@ -400,8 +404,12 @@ uint64_t helper_fmuladd_d(CPULoongArchState *env, uint64_t 
fj,
                           uint64_t fk, uint64_t fa, uint32_t flag)
 {
     uint64_t fd;
+    uint32_t neg_res;
 
+    neg_res = flag & float_muladd_negate_result;
+    flag ^= neg_res;
     fd = float64_muladd(fj, fk, fa, flag, &env->fp_status);
+    fd |= neg_res ? 0x8000000000000000ULL : 0;
     update_fcsr0(env, GETPC());
     return fd;
 }
diff --git a/target/loongarch/tcg/vec_helper.c 
b/target/loongarch/tcg/vec_helper.c
index 3faf52cbc4..c48e3662d1 100644
--- a/target/loongarch/tcg/vec_helper.c
+++ b/target/loongarch/tcg/vec_helper.c
@@ -2446,10 +2446,15 @@ void HELPER(NAME)(void *vd, void *vj, void *vk, void 
*va,                    \
     VReg *Vk = (VReg *)vk;                                                   \
     VReg *Va = (VReg *)va;                                                   \
     int oprsz = simd_oprsz(desc);                                            \
+    uint32_t flag = flags;                                                   \
+    uint32_t neg_res;                                                        \
                                                                              \
+    neg_res = flag & float_muladd_negate_result;                             \
+    flag ^= neg_res;                                                         \
     vec_clear_cause(env);                                                    \
     for (i = 0; i < oprsz / (BIT / 8); i++) {                                \
-        Vd->E(i) = FN(Vj->E(i), Vk->E(i), Va->E(i), flags, &env->fp_status); \
+        Vd->E(i) = FN(Vj->E(i), Vk->E(i), Va->E(i), flag, &env->fp_status);  \
+        Vd->E(i) |= neg_res ? (1ULL << (BIT - 1)) : 0;                       \
         vec_update_fcsr0(env, GETPC());                                      \
     }                                                                        \
 }
diff --git a/tests/tcg/loongarch64/Makefile.target 
b/tests/tcg/loongarch64/Makefile.target
index 00030a1026..e3554a500e 100644
--- a/tests/tcg/loongarch64/Makefile.target
+++ b/tests/tcg/loongarch64/Makefile.target
@@ -16,5 +16,7 @@ LOONGARCH64_TESTS  += test_fclass
 LOONGARCH64_TESTS  += test_fpcom
 LOONGARCH64_TESTS  += test_pcadd
 LOONGARCH64_TESTS  += test_fcsr
+LOONGARCH64_TESTS  += test_fnmsub
+LOONGARCH64_TESTS  += test_vfnmsub
 
 TESTS += $(LOONGARCH64_TESTS)
diff --git a/tests/tcg/loongarch64/test_fnmsub.c 
b/tests/tcg/loongarch64/test_fnmsub.c
new file mode 100644
index 0000000000..0c8514f33d
--- /dev/null
+++ b/tests/tcg/loongarch64/test_fnmsub.c
@@ -0,0 +1,20 @@
+#include <assert.h>
+#include <stdint.h>
+#include <fenv.h>
+
+int main()
+{
+    double x, y, z;
+
+    *(uint64_t *)&x = 0x4ff0000000000000UL;
+    *(uint64_t *)&y = 0x4ff0000000000000UL;
+    *(uint64_t *)&z = 0x2ff0000000000000UL;
+
+    fesetround(FE_DOWNWARD);
+    asm("fnmsub.d %[x], %[x], %[y], %[z]\n\t"
+        :[x]"+f"(x)
+        :[y]"f"(y), [z]"f"(z));
+
+    assert(*(uint64_t *)&x == 0xdfefffffffffffffUL);
+    return 0;
+}
diff --git a/tests/tcg/loongarch64/test_vfnmsub.c 
b/tests/tcg/loongarch64/test_vfnmsub.c
new file mode 100644
index 0000000000..8c332674ae
--- /dev/null
+++ b/tests/tcg/loongarch64/test_vfnmsub.c
@@ -0,0 +1,27 @@
+#include <assert.h>
+#include <stdint.h>
+#include <fenv.h>
+
+int main()
+{
+    uint64_t x, y, z;
+
+    x = 0x4ff0000000000000UL;
+    y = 0x4ff0000000000000UL;
+    z = 0x2ff0000000000000UL;
+
+    fesetround(FE_DOWNWARD);
+    asm("vreplgr2vr.d $vr0, %[x]\n\t"
+        "vreplgr2vr.d $vr1, %[y]\n\t"
+        "vreplgr2vr.d $vr2, %[z]\n\t"
+        "vfnmsub.d $vr0, $vr0, $vr1, $vr2\n\t"
+        "vpickve2gr.d %[x], $vr0, 0\n\t"
+        "vpickve2gr.d %[y], $vr0, 1\n\t"
+        :[x]"+&r"(x), [y]"+&r"(y)
+        :[z]"r"(z)
+        :"$f0", "$f1", "$f2");
+
+    assert(x == 0xdfefffffffffffffUL);
+    assert(y == 0xdfefffffffffffffUL);
+    return 0;
+}
-- 
2.49.0


Reply via email to