No public description PiperOrigin-RevId: 932550923 Change-Id: I48865f964577c2e2918acb706198934ec3e0c611
diff --git a/riscv/riscv_vector_instruction_helpers.h b/riscv/riscv_vector_instruction_helpers.h index e26aebe..0386225 100644 --- a/riscv/riscv_vector_instruction_helpers.h +++ b/riscv/riscv_vector_instruction_helpers.h
@@ -21,10 +21,12 @@ #include <limits> #include <optional> #include <tuple> +#include <type_traits> #include "absl/log/log.h" #include "absl/strings/str_cat.h" #include "mpact/sim/generic/instruction.h" +#include "mpact/sim/generic/type_helpers.h" #include "riscv/riscv_register.h" #include "riscv/riscv_state.h" #include "riscv/riscv_vector_state.h" @@ -35,6 +37,23 @@ using ::mpact::sim::generic::GetInstructionSource; +template <typename T> +inline constexpr bool IsMpactFpType = + std::is_floating_point<T>::value || + std::is_same<typename std::decay<T>::type, + mpact::sim::generic::HalfFP>::value; + +template <typename T> +inline T CanonicalizeVectorNaN(T value) { + if constexpr (IsMpactFpType<T>) { + if (mpact::sim::generic::FPTypeInfo<T>::IsNaN(value)) { + auto nan_val = mpact::sim::generic::FPTypeInfo<T>::kCanonicalNaN; + return *reinterpret_cast<T*>(&nan_val); + } + } + return value; +} + // This helper function handles the case of instructions that target a vector // mask. // It clears the masked bit and uses the mask value in the @@ -214,7 +233,7 @@ auto result = op(operation_mask); if (mask_value) { - dest_span[i] = result; + dest_span[i] = CanonicalizeVectorNaN<Vd>(result); } vector_index++; } @@ -285,7 +304,7 @@ if (mask_value) { // Compute result. Vs2 vs2 = GetInstructionSource<Vs2>(inst, 0, vector_index); - dest_span[i] = op(vs2); + dest_span[i] = CanonicalizeVectorNaN<Vd>(op(vs2)); } vector_index++; } @@ -359,7 +378,7 @@ // Compute result. Vs2 vs2 = GetInstructionSource<Vs2>(inst, 0, vector_index); auto [value, flag] = op(vs2); - dest_span[i] = value; + dest_span[i] = CanonicalizeVectorNaN<Vd>(value); fflags |= flag; } vector_index++; @@ -442,7 +461,7 @@ (vector_scalar ? 0 : vector_index)); auto value = op(vs2, vs1, mask_value); if (value.has_value()) { - dest_span[i] = value.value(); + dest_span[i] = CanonicalizeVectorNaN<Vd>(value.value()); } else if (mask_value) { // If there is no value returned, but the mask_value is true, check // to see if there was an exception. @@ -543,7 +562,7 @@ (vector_scalar ? 0 : vector_index)); if (mask_value) { auto [value, flag] = op(vs2, vs1); - dest_span[i] = value; + dest_span[i] = CanonicalizeVectorNaN<Vd>(value); fflags |= flag; if (rv_vector->vector_exception()) { rv_vector->set_vstart(vector_index); @@ -629,7 +648,7 @@ (vector_scalar ? 0 : vector_index)); Vd vd = GetInstructionSource<Vd>(inst, 2, vector_index); if (mask_value) { - dest_span[i] = op(vs2, vs1, vd); + dest_span[i] = CanonicalizeVectorNaN<Vd>(op(vs2, vs1, vd)); } vector_index++; } @@ -685,7 +704,7 @@ auto* dest_op = static_cast<RV32VectorDestinationOperand*>(inst->Destination(0)); auto dest_db = dest_op->CopyDataBuffer(); - dest_db->Set<Vd>(0, accumulator); + dest_db->Set<Vd>(0, CanonicalizeVectorNaN<Vd>(accumulator)); dest_db->Submit(); rv_vector->clear_vstart(); }
diff --git a/riscv/test/riscv_vector_fp_instructions_test.cc b/riscv/test/riscv_vector_fp_instructions_test.cc index 98d1673..cd390c9 100644 --- a/riscv/test/riscv_vector_fp_instructions_test.cc +++ b/riscv/test/riscv_vector_fp_instructions_test.cc
@@ -307,6 +307,289 @@ } } + // Tests if vector arithmetic instructions properly canonicalize NaNs. + template <typename Vd, typename Vs2, typename Vs1> + void CanonicalNaNBitwiseTestHelperVV(absl::string_view name, int sew, + bool has_fflags = false) { + constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2); + constexpr int vs1_size = kVectorLengthInBytes / sizeof(Vs1); + Vs2 vs2_value[vs2_size * 8]; + Vs1 vs1_value[vs1_size * 8]; + auto vs2_span = Span<Vs2>(vs2_value); + auto vs1_span = Span<Vs1>(vs1_value); + + AppendVectorRegisterOperands({kVs2, kVs1, kVmask}, {kVd}); + if (has_fflags) { + auto* flag_op = + rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags"); + instruction_->AppendDestination(flag_op); + } + SetVectorRegisterValues<uint8_t>( + {{kVmaskName, Span<const uint8_t>(kA5Mask)}}); + vreg_[kVmask]->data_buffer()->Set<uint8_t>(0, 0xff); + + // Fill vs2 and vs1 elements with non-canonical NaNs + using Vs2Int = typename FPTypeInfo<Vs2>::IntType; + using Vs1Int = typename FPTypeInfo<Vs1>::IntType; + Vs2Int non_canonical_nan_vs2 = + (sizeof(Vs2) == 4) ? 0xffc00000 : 0xfff8000000000000ULL; + Vs1Int non_canonical_nan_vs1 = + (sizeof(Vs1) == 4) ? 0xffc00000 : 0xfff8000000000000ULL; + Vs2 non_canonical_fp_nan_vs2 = + *reinterpret_cast<Vs2*>(&non_canonical_nan_vs2); + Vs1 non_canonical_fp_nan_vs1 = + *reinterpret_cast<Vs1*>(&non_canonical_nan_vs1); + + for (int i = 0; i < vs2_size * 8; i++) + vs2_span[i] = non_canonical_fp_nan_vs2; + for (int i = 0; i < vs1_size * 8; i++) + vs1_span[i] = non_canonical_fp_nan_vs1; + + for (int i = 0; i < 8; i++) { + auto vs2_name = absl::StrCat("v", kVs2 + i); + auto vs1_name = absl::StrCat("v", kVs1 + i); + SetVectorRegisterValues<Vs2>( + {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}}); + SetVectorRegisterValues<Vs1>( + {{vs1_name, vs1_span.subspan(vs1_size * i, vs1_size)}}); + } + + int byte_sew = sew / 8; + uint32_t vtype = + (kSewSettingsByByteSize[byte_sew] << 3) | kLmulSettings[3]; // LMUL = 1 + int vlen = kVectorLengthInBytes / byte_sew; + ConfigureVectorUnit(vtype, vlen); + rv_vector_->set_vstart(0); + + instruction_->Execute(); + + auto* dest_db = vreg_[kVd]->data_buffer(); + using VdInt = typename FPTypeInfo<Vd>::IntType; + VdInt expected_bits = FPTypeInfo<Vd>::kCanonicalNaN; + for (int i = 0; i < 4; i++) { // Check first 4 elements which are unmasked + VdInt result_bits = dest_db->Get<VdInt>(i); + EXPECT_EQ(result_bits, expected_bits) + << name << " failed to canonicalize NaN at index " << i; + } + } + + template <typename Vd, typename Vs2, typename Fs1> + void CanonicalNaNBitwiseTestHelperVX(absl::string_view name, int sew, + bool has_fflags = false) { + constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2); + Vs2 vs2_value[vs2_size * 8]; + auto vs2_span = Span<Vs2>(vs2_value); + + AppendVectorRegisterOperands({kVs2}, {kVd}); + AppendRegisterOperands<RVScalarRegister>({kFs1Name}, {}); + if (has_fflags) { + auto* flag_op = + rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags"); + instruction_->AppendDestination(flag_op); + } + AppendVectorRegisterOperands({kVmask}, {}); + + SetVectorRegisterValues<uint8_t>( + {{kVmaskName, Span<const uint8_t>(kA5Mask)}}); + vreg_[kVmask]->data_buffer()->Set<uint8_t>(0, 0xff); + + // Fill vs2 elements with non-canonical NaNs + using Vs2Int = typename FPTypeInfo<Vs2>::IntType; + using Fs1Int = typename FPTypeInfo<Fs1>::IntType; + Vs2Int non_canonical_nan_vs2 = + (sizeof(Vs2) == 4) ? 0xffc00000 : 0xfff8000000000000ULL; + Fs1Int non_canonical_nan_fs1 = + (sizeof(Fs1) == 4) ? 0xffc00000 : 0xfff8000000000000ULL; + Vs2 non_canonical_fp_nan_vs2 = + *reinterpret_cast<Vs2*>(&non_canonical_nan_vs2); + Fs1 non_canonical_fp_nan_fs1 = + *reinterpret_cast<Fs1*>(&non_canonical_nan_fs1); + + for (int i = 0; i < vs2_size * 8; i++) + vs2_span[i] = non_canonical_fp_nan_vs2; + + for (int i = 0; i < 8; i++) { + auto vs2_name = absl::StrCat("v", kVs2 + i); + SetVectorRegisterValues<Vs2>( + {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}}); + } + + typename RVScalarRegister::ValueType fs1_reg_value = + NaNBox<Fs1, typename RVScalarRegister::ValueType>( + non_canonical_fp_nan_fs1); + SetRegisterValues<typename RVScalarRegister::ValueType, RVScalarRegister>( + {{kFs1Name, fs1_reg_value}}); + + int byte_sew = sew / 8; + uint32_t vtype = + (kSewSettingsByByteSize[byte_sew] << 3) | kLmulSettings[3]; // LMUL = 1 + int vlen = kVectorLengthInBytes / byte_sew; + ConfigureVectorUnit(vtype, vlen); + rv_vector_->set_vstart(0); + + instruction_->Execute(); + + auto* dest_db = vreg_[kVd]->data_buffer(); + using VdInt = typename FPTypeInfo<Vd>::IntType; + VdInt expected_bits = FPTypeInfo<Vd>::kCanonicalNaN; + for (int i = 0; i < 4; i++) { // Check first 4 elements which are unmasked + VdInt result_bits = dest_db->Get<VdInt>(i); + EXPECT_EQ(result_bits, expected_bits) + << name << " failed to canonicalize NaN at index " << i; + } + } + + template <typename Vd, typename Vs2, typename Vs1> + void CanonicalNaNBitwiseTestHelperTernaryVV(absl::string_view name, int sew, + bool has_fflags = false) { + constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2); + constexpr int vs1_size = kVectorLengthInBytes / sizeof(Vs1); + constexpr int vd_size = kVectorLengthInBytes / sizeof(Vd); + Vs2 vs2_value[vs2_size * 8]; + Vs1 vs1_value[vs1_size * 8]; + Vd vd_value[vd_size * 8]; + auto vs2_span = Span<Vs2>(vs2_value); + auto vs1_span = Span<Vs1>(vs1_value); + auto vd_span = Span<Vd>(vd_value); + + AppendVectorRegisterOperands({kVs2, kVs1, kVd, kVmask}, {kVd}); + if (has_fflags) { + auto* flag_op = + rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags"); + instruction_->AppendDestination(flag_op); + } + SetVectorRegisterValues<uint8_t>( + {{kVmaskName, Span<const uint8_t>(kA5Mask)}}); + vreg_[kVmask]->data_buffer()->Set<uint8_t>(0, 0xff); + + // Fill elements with non-canonical NaNs + using Vs2Int = typename FPTypeInfo<Vs2>::IntType; + using Vs1Int = typename FPTypeInfo<Vs1>::IntType; + using VdInt = typename FPTypeInfo<Vd>::IntType; + Vs2Int non_canonical_nan_vs2 = + (sizeof(Vs2) == 4) ? 0xffc00000 : 0xfff8000000000000ULL; + Vs1Int non_canonical_nan_vs1 = + (sizeof(Vs1) == 4) ? 0xffc00000 : 0xfff8000000000000ULL; + VdInt non_canonical_nan_vd = + (sizeof(Vd) == 4) ? 0xffc00000 : 0xfff8000000000000ULL; + Vs2 non_canonical_fp_nan_vs2 = + *reinterpret_cast<Vs2*>(&non_canonical_nan_vs2); + Vs1 non_canonical_fp_nan_vs1 = + *reinterpret_cast<Vs1*>(&non_canonical_nan_vs1); + Vd non_canonical_fp_nan_vd = *reinterpret_cast<Vd*>(&non_canonical_nan_vd); + + for (int i = 0; i < vs2_size * 8; i++) + vs2_span[i] = non_canonical_fp_nan_vs2; + for (int i = 0; i < vs1_size * 8; i++) + vs1_span[i] = non_canonical_fp_nan_vs1; + for (int i = 0; i < vd_size * 8; i++) vd_span[i] = non_canonical_fp_nan_vd; + + for (int i = 0; i < 8; i++) { + auto vs2_name = absl::StrCat("v", kVs2 + i); + auto vs1_name = absl::StrCat("v", kVs1 + i); + auto vd_name = absl::StrCat("v", kVd + i); + SetVectorRegisterValues<Vs2>( + {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}}); + SetVectorRegisterValues<Vs1>( + {{vs1_name, vs1_span.subspan(vs1_size * i, vs1_size)}}); + SetVectorRegisterValues<Vd>( + {{vd_name, vd_span.subspan(vd_size * i, vd_size)}}); + } + + int byte_sew = sew / 8; + uint32_t vtype = + (kSewSettingsByByteSize[byte_sew] << 3) | kLmulSettings[3]; // LMUL = 1 + int vlen = kVectorLengthInBytes / byte_sew; + ConfigureVectorUnit(vtype, vlen); + rv_vector_->set_vstart(0); + + instruction_->Execute(); + + auto* dest_db = vreg_[kVd]->data_buffer(); + VdInt expected_bits = FPTypeInfo<Vd>::kCanonicalNaN; + for (int i = 0; i < 4; i++) { // Check first 4 elements which are unmasked + VdInt result_bits = dest_db->Get<VdInt>(i); + EXPECT_EQ(result_bits, expected_bits) + << name << " failed to canonicalize NaN at index " << i; + } + } + + template <typename Vd, typename Vs2, typename Fs1> + void CanonicalNaNBitwiseTestHelperTernaryVX(absl::string_view name, int sew, + bool has_fflags = false) { + constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2); + constexpr int vd_size = kVectorLengthInBytes / sizeof(Vd); + Vs2 vs2_value[vs2_size * 8]; + Vd vd_value[vd_size * 8]; + auto vs2_span = Span<Vs2>(vs2_value); + auto vd_span = Span<Vd>(vd_value); + + AppendVectorRegisterOperands({kVs2}, {kVd}); + AppendRegisterOperands<RVScalarRegister>({kFs1Name}, {}); + AppendVectorRegisterOperands({kVd, kVmask}, {kVd}); + if (has_fflags) { + auto* flag_op = + rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags"); + instruction_->AppendDestination(flag_op); + } + + SetVectorRegisterValues<uint8_t>( + {{kVmaskName, Span<const uint8_t>(kA5Mask)}}); + vreg_[kVmask]->data_buffer()->Set<uint8_t>(0, 0xff); + + // Fill elements with non-canonical NaNs + using Vs2Int = typename FPTypeInfo<Vs2>::IntType; + using Fs1Int = typename FPTypeInfo<Fs1>::IntType; + using VdInt = typename FPTypeInfo<Vd>::IntType; + Vs2Int non_canonical_nan_vs2 = + (sizeof(Vs2) == 4) ? 0xffc00000 : 0xfff8000000000000ULL; + Fs1Int non_canonical_nan_fs1 = + (sizeof(Fs1) == 4) ? 0xffc00000 : 0xfff8000000000000ULL; + VdInt non_canonical_nan_vd = + (sizeof(Vd) == 4) ? 0xffc00000 : 0xfff8000000000000ULL; + Vs2 non_canonical_fp_nan_vs2 = + *reinterpret_cast<Vs2*>(&non_canonical_nan_vs2); + Fs1 non_canonical_fp_nan_fs1 = + *reinterpret_cast<Fs1*>(&non_canonical_nan_fs1); + Vd non_canonical_fp_nan_vd = *reinterpret_cast<Vd*>(&non_canonical_nan_vd); + + for (int i = 0; i < vs2_size * 8; i++) + vs2_span[i] = non_canonical_fp_nan_vs2; + for (int i = 0; i < vd_size * 8; i++) vd_span[i] = non_canonical_fp_nan_vd; + + for (int i = 0; i < 8; i++) { + auto vs2_name = absl::StrCat("v", kVs2 + i); + auto vd_name = absl::StrCat("v", kVd + i); + SetVectorRegisterValues<Vs2>( + {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}}); + SetVectorRegisterValues<Vd>( + {{vd_name, vd_span.subspan(vd_size * i, vd_size)}}); + } + + typename RVScalarRegister::ValueType fs1_reg_value = + NaNBox<Fs1, typename RVScalarRegister::ValueType>( + non_canonical_fp_nan_fs1); + SetRegisterValues<typename RVScalarRegister::ValueType, RVScalarRegister>( + {{kFs1Name, fs1_reg_value}}); + + int byte_sew = sew / 8; + uint32_t vtype = + (kSewSettingsByByteSize[byte_sew] << 3) | kLmulSettings[3]; // LMUL = 1 + int vlen = kVectorLengthInBytes / byte_sew; + ConfigureVectorUnit(vtype, vlen); + rv_vector_->set_vstart(0); + + instruction_->Execute(); + + auto* dest_db = vreg_[kVd]->data_buffer(); + VdInt expected_bits = FPTypeInfo<Vd>::kCanonicalNaN; + for (int i = 0; i < 4; i++) { // Check first 4 elements which are unmasked + VdInt result_bits = dest_db->Get<VdInt>(i); + EXPECT_EQ(result_bits, expected_bits) + << name << " failed to canonicalize NaN at index " << i; + } + } + // Floating point test needs to ensure to use the fp special values (inf, NaN // etc.) during testing, not just random values. This function handles vector // scalar instructions. @@ -505,6 +788,12 @@ TEST_F(RiscVFPInstructionsTest, Vfadd) { // Vector-vector. SetSemanticFunction(&Vfadd); + CanonicalNaNBitwiseTestHelperVV<float, float, float>("Vfadd_vv32_nan", 32); + ResetInstruction(); + SetSemanticFunction(&Vfadd); + CanonicalNaNBitwiseTestHelperVV<double, double, double>("Vfadd_vv64_nan", 64); + ResetInstruction(); + SetSemanticFunction(&Vfadd); BinaryOpFPTestHelperVV<float, float, float>( "Vfadd_vv32", /*sew*/ 32, instruction_, /*delta_position*/ 32, [](float vs2, float vs1) -> float { return vs2 + vs1; }); @@ -516,6 +805,12 @@ // Vector-scalar. ResetInstruction(); SetSemanticFunction(&Vfadd); + CanonicalNaNBitwiseTestHelperVX<float, float, float>("Vfadd_vx32_nan", 32); + ResetInstruction(); + SetSemanticFunction(&Vfadd); + CanonicalNaNBitwiseTestHelperVX<double, double, double>("Vfadd_vx64_nan", 64); + ResetInstruction(); + SetSemanticFunction(&Vfadd); BinaryOpFPTestHelperVX<float, float, float, RVScalarRegister>( "Vfadd_vx32", /*sew*/ 32, instruction_, /*delta_position*/ 32, [](float vs2, float vs1) -> float { return vs2 + vs1; }); @@ -882,6 +1177,14 @@ TEST_F(RiscVFPInstructionsTest, Vfmacc) { // Vector-vector. SetSemanticFunction(&Vfmacc); + CanonicalNaNBitwiseTestHelperTernaryVV<float, float, float>("Vfmacc_vv32_nan", + 32); + ResetInstruction(); + SetSemanticFunction(&Vfmacc); + CanonicalNaNBitwiseTestHelperTernaryVV<double, double, double>( + "Vfmacc_vv64_nan", 64); + ResetInstruction(); + SetSemanticFunction(&Vfmacc); TernaryOpFPTestHelperVV<float, float, float>( "Vfmacc_vv32", /*sew*/ 32, instruction_, /*delta_position*/ 32, [](float vs2, float vs1, float vd) -> float { @@ -897,6 +1200,14 @@ // Vector-scalar. ResetInstruction(); SetSemanticFunction(&Vfmacc); + CanonicalNaNBitwiseTestHelperTernaryVX<float, float, float>("Vfmacc_vx32_nan", + 32); + ResetInstruction(); + SetSemanticFunction(&Vfmacc); + CanonicalNaNBitwiseTestHelperTernaryVX<double, double, double>( + "Vfmacc_vx64_nan", 64); + ResetInstruction(); + SetSemanticFunction(&Vfmacc); TernaryOpFPTestHelperVX<float, float, float>( "Vfmacc_vx32", /*sew*/ 32, instruction_, /*delta_position*/ 32, [](float vs2, float vs1, float vd) -> float { @@ -1291,6 +1602,14 @@ TEST_F(RiscVFPInstructionsTest, Vfmax) { // Vector-vector. SetSemanticFunction(&Vfmax); + CanonicalNaNBitwiseTestHelperVV<float, float, float>("Vfmax_vv32_nan", 32, + true); + ResetInstruction(); + SetSemanticFunction(&Vfmax); + CanonicalNaNBitwiseTestHelperVV<double, double, double>("Vfmax_vv64_nan", 64, + true); + ResetInstruction(); + SetSemanticFunction(&Vfmax); BinaryOpWithFflagsFPTestHelperVV<float, float, float>( "Vfmax_vv32", /*sew*/ 32, instruction_, /*delta_position*/ 32, [](float vs2, float vs1) -> std::tuple<float, uint32_t> { @@ -1313,6 +1632,14 @@ // Vector-scalar. ResetInstruction(); SetSemanticFunction(&Vfmax); + CanonicalNaNBitwiseTestHelperVX<float, float, float>("Vfmax_vx32_nan", 32, + true); + ResetInstruction(); + SetSemanticFunction(&Vfmax); + CanonicalNaNBitwiseTestHelperVX<double, double, double>("Vfmax_vx64_nan", 64, + true); + ResetInstruction(); + SetSemanticFunction(&Vfmax); BinaryOpWithFflagsFPTestHelperVX<float, float, float, RVScalarRegister>( "Vfmax_vx32", /*sew*/ 32, instruction_, /*delta_position*/ 32, [](float vs2, float vs1) -> std::tuple<float, uint32_t> { @@ -1392,4 +1719,55 @@ }); } +TEST_F(RiscVFPInstructionsTest, VfrdivCanonicalNaNBitwise) { + SetSemanticFunction(&Vfrdiv); + + constexpr int vs2_size = kVectorLengthInBytes / sizeof(float); + float vs2_value[vs2_size * 8]; + auto vs2_span = Span<float>(vs2_value); + + AppendVectorRegisterOperands({kVs2}, {kVd}); + AppendRegisterOperands<RVScalarRegister>({kFs1Name}, {}); + AppendVectorRegisterOperands({kVmask}, {}); + + SetVectorRegisterValues<uint8_t>( + {{kVmaskName, Span<const uint8_t>(kA5Mask)}}); + vreg_[kVmask]->data_buffer()->Set<uint8_t>(0, 0xff); + + // Fill vs2 elements with non-canonical float single-precision quiet NaN + // payload bits (0xffc00000) + uint32_t non_canonical_nan = 0xffc00000; + float non_canonical_float_nan = *reinterpret_cast<float*>(&non_canonical_nan); + for (int i = 0; i < vs2_size * 8; i++) { + vs2_span[i] = non_canonical_float_nan; + } + + for (int i = 0; i < 8; i++) { + auto vs2_name = absl::StrCat("v", kVs2 + i); + SetVectorRegisterValues<float>( + {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}}); + } + + // Set scalar operand fs1 value + float fs1_val = 1.0f; + typename RVScalarRegister::ValueType fs1_reg_value = + NaNBox<float, typename RVScalarRegister::ValueType>(fs1_val); + SetRegisterValues<typename RVScalarRegister::ValueType, RVScalarRegister>( + {{kFs1Name, fs1_reg_value}}); + + // sew = 32 bits = 4 bytes, sew_field = 2 + uint32_t vtype = + (kSewSettingsByByteSize[4] << 3) | kLmulSettings[3]; // LMUL = 1 + ConfigureVectorUnit(vtype, 4); + rv_vector_->set_vstart(0); + + instruction_->Execute(); + + auto* dest_db = vreg_[kVd]->data_buffer(); + for (int i = 0; i < 4; i++) { + uint32_t result_bits = dest_db->Get<uint32_t>(i); + EXPECT_EQ(result_bits, 0x7fc00000); + } +} + } // namespace
diff --git a/riscv/test/riscv_vector_fp_reduction_instructions_test.cc b/riscv/test/riscv_vector_fp_reduction_instructions_test.cc index bd7535e..355ad22 100644 --- a/riscv/test/riscv_vector_fp_reduction_instructions_test.cc +++ b/riscv/test/riscv_vector_fp_reduction_instructions_test.cc
@@ -65,6 +65,65 @@ class RiscVFPReductionInstructionsTest : public ::mpact::sim::riscv::test::RiscVFPInstructionsTestBase { public: + // Tests if vector arithmetic instructions properly canonicalize NaNs. + template <typename Vd, typename Vs2, typename Vs1> + void CanonicalNaNBitwiseTestHelperReduction(absl::string_view name, int sew) { + int byte_sew = sew / 8; + constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2); + constexpr int vs1_size = kVectorLengthInBytes / sizeof(Vs1); + Vs2 vs2_value[vs2_size * 8]; + auto vs2_span = Span<Vs2>(vs2_value); + Vs1 vs1_value[vs1_size * 8]; + auto vs1_span = Span<Vs1>(vs1_value); + + AppendVectorRegisterOperands({kVs2, kVs1, kVmask}, {kVd}); + auto mask_span = Span<const uint8_t>(kA5Mask); + SetVectorRegisterValues<uint8_t>({{kVmaskName, mask_span}}); + + // Fill elements with non-canonical NaNs + using Vs2Int = typename FPTypeInfo<Vs2>::IntType; + using Vs1Int = typename FPTypeInfo<Vs1>::IntType; + Vs2Int non_canonical_nan_vs2 = + (sizeof(Vs2) == 4) ? 0xffc00000 : 0xfff8000000000000ULL; + Vs1Int non_canonical_nan_vs1 = + (sizeof(Vs1) == 4) ? 0xffc00000 : 0xfff8000000000000ULL; + Vs2 non_canonical_fp_nan_vs2 = + *reinterpret_cast<Vs2*>(&non_canonical_nan_vs2); + Vs1 non_canonical_fp_nan_vs1 = + *reinterpret_cast<Vs1*>(&non_canonical_nan_vs1); + + for (int i = 0; i < vs2_size * 8; i++) + vs2_span[i] = non_canonical_fp_nan_vs2; + for (int i = 0; i < vs1_size * 8; i++) + vs1_span[i] = non_canonical_fp_nan_vs1; + + for (int i = 0; i < 8; i++) { + auto vs2_name = absl::StrCat("v", kVs2 + i); + auto vs1_name = absl::StrCat("v", kVs1 + i); + SetVectorRegisterValues<Vs2>( + {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}}); + SetVectorRegisterValues<Vs1>( + {{vs1_name, vs1_span.subspan(vs1_size * i, vs1_size)}}); + } + + uint32_t vtype = + (kSewSettingsByByteSize[byte_sew] << 3) | kLmulSettings[3]; // LMUL = 1 + int vlen = kVectorLengthInBytes / byte_sew; + ConfigureVectorUnit(vtype, vlen); + + rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(0)); + ClearVectorRegisterGroup(kVd, 8); + + instruction_->Execute(); + + auto reg_val = vreg_[kVd]->data_buffer()->Get<Vd>(0); + using VdInt = typename FPTypeInfo<Vd>::IntType; + VdInt result_bits = *reinterpret_cast<VdInt*>(®_val); + VdInt expected_bits = FPTypeInfo<Vd>::kCanonicalNaN; + EXPECT_EQ(result_bits, expected_bits) + << name << " failed to canonicalize NaN in reduction"; + } + // Helper function for floating point reduction operations. template <typename Vd, typename Vs2, typename Vs1> void ReductionOpFPTestHelper(absl::string_view name, int sew, @@ -171,6 +230,14 @@ // Test vector floating point sum reduction. TEST_F(RiscVFPReductionInstructionsTest, Vfredosum) { SetSemanticFunction(&Vfredosum); + CanonicalNaNBitwiseTestHelperReduction<float, float, float>( + "Vfredosum_32_nan", 32); + ResetInstruction(); + SetSemanticFunction(&Vfredosum); + CanonicalNaNBitwiseTestHelperReduction<double, double, double>( + "Vfredosum_64_nan", 64); + ResetInstruction(); + SetSemanticFunction(&Vfredosum); ReductionOpFPTestHelper<float, float, float>( "Vfredosum_32", /*sew*/ 32, instruction_, /*delta_position*/ 32, [](float val0, float val1) -> float { return val0 + val1; }); @@ -215,6 +282,14 @@ // Test vector floating point min reduction. TEST_F(RiscVFPReductionInstructionsTest, Vfredmin) { SetSemanticFunction(&Vfredmin); + CanonicalNaNBitwiseTestHelperReduction<float, float, float>("Vfredmin_32_nan", + 32); + ResetInstruction(); + SetSemanticFunction(&Vfredmin); + CanonicalNaNBitwiseTestHelperReduction<double, double, double>( + "Vfredmin_64_nan", 64); + ResetInstruction(); + SetSemanticFunction(&Vfredmin); ReductionOpFPTestHelper<float, float, float>( "Vfredmin_32", /*sew*/ 32, instruction_, /*delta_position*/ 32, [](float val0, float val1) -> float { @@ -238,6 +313,14 @@ // Test vector floating point max reduction. TEST_F(RiscVFPReductionInstructionsTest, Vfredmax) { SetSemanticFunction(&Vfredmax); + CanonicalNaNBitwiseTestHelperReduction<float, float, float>("Vfredmax_32_nan", + 32); + ResetInstruction(); + SetSemanticFunction(&Vfredmax); + CanonicalNaNBitwiseTestHelperReduction<double, double, double>( + "Vfredmax_64_nan", 64); + ResetInstruction(); + SetSemanticFunction(&Vfredmax); ReductionOpFPTestHelper<float, float, float>( "Vfredmin_32", /*sew*/ 32, instruction_, /*delta_position*/ 32, [](float val0, float val1) -> float {
diff --git a/riscv/test/riscv_vector_fp_unary_instructions_test.cc b/riscv/test/riscv_vector_fp_unary_instructions_test.cc index 04540fe..a15e57b 100644 --- a/riscv/test/riscv_vector_fp_unary_instructions_test.cc +++ b/riscv/test/riscv_vector_fp_unary_instructions_test.cc
@@ -269,6 +269,59 @@ } } + // Tests if unary fp vector instructions properly canonicalize NaNs. + template <typename Vd, typename Vs2> + void CanonicalNaNBitwiseTestHelperUnaryV(absl::string_view name, int sew, + bool has_fflags = false) { + constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2); + Vs2 vs2_value[vs2_size * 8]; + auto vs2_span = Span<Vs2>(vs2_value); + + AppendVectorRegisterOperands({kVs2, kVmask}, {kVd}); + if (has_fflags) { + auto* flag_op = + rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags"); + instruction_->AppendDestination(flag_op); + } + SetVectorRegisterValues<uint8_t>( + {{kVmaskName, Span<const uint8_t>(kA5Mask)}}); + vreg_[kVmask]->data_buffer()->Set<uint8_t>(0, 0xff); + + // Fill elements with non-canonical NaNs + using Vs2Int = typename FPTypeInfo<Vs2>::IntType; + Vs2Int non_canonical_nan_vs2 = + (sizeof(Vs2) == 4) ? 0xffc00000 : 0xfff8000000000000ULL; + Vs2 non_canonical_fp_nan_vs2 = + *reinterpret_cast<Vs2*>(&non_canonical_nan_vs2); + + for (int i = 0; i < vs2_size * 8; i++) + vs2_span[i] = non_canonical_fp_nan_vs2; + + for (int i = 0; i < 8; i++) { + auto vs2_name = absl::StrCat("v", kVs2 + i); + SetVectorRegisterValues<Vs2>( + {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}}); + } + + int byte_sew = sew / 8; + uint32_t vtype = + (kSewSettingsByByteSize[byte_sew] << 3) | kLmulSettings[3]; // LMUL = 1 + int vlen = kVectorLengthInBytes / byte_sew; + ConfigureVectorUnit(vtype, vlen); + rv_vector_->set_vstart(0); + + instruction_->Execute(); + + auto* dest_db = vreg_[kVd]->data_buffer(); + using VdInt = typename FPTypeInfo<Vd>::IntType; + VdInt expected_bits = FPTypeInfo<Vd>::kCanonicalNaN; + for (int i = 0; i < 4; i++) { // Check first 4 elements which are unmasked + VdInt result_bits = dest_db->Get<VdInt>(i); + EXPECT_EQ(result_bits, expected_bits) + << name << " failed to canonicalize NaN at index " << i; + } + } + // Floating point test needs to ensure to use the fp special values (inf, NaN // etc.) during testing, not just random values. template <typename Vd, typename Vs2> @@ -921,6 +974,14 @@ // Test square root instruction. TEST_F(RiscVFPUnaryInstructionsTest, Vfsqrtv) { SetSemanticFunction(&Vfsqrtv); + CanonicalNaNBitwiseTestHelperUnaryV<float, float>("Vfsqrt.v_32_nan", 32, + true); + ResetInstruction(); + SetSemanticFunction(&Vfsqrtv); + CanonicalNaNBitwiseTestHelperUnaryV<double, double>("Vfsqrt.v_64_nan", 64, + true); + ResetInstruction(); + SetSemanticFunction(&Vfsqrtv); UnaryOpWithFflagsFPTestHelperV<float, float>( "Vfsqrt.v_32", /*sew*/ 32, instruction_, /*delta_position*/ 32, [](float vs2) -> std::tuple<float, uint32_t> {