zfh: fmin, fmax, fsgn semantic functions

PiperOrigin-RevId: 755956117
Change-Id: I4d8eaf54f63efb1f79d8861e30879a15f1b3075d
diff --git a/riscv/riscv_zfh.isa b/riscv/riscv_zfh.isa
index f4461f7..11e9a89 100644
--- a/riscv/riscv_zfh.isa
+++ b/riscv/riscv_zfh.isa
@@ -136,5 +136,25 @@
       resources: {next_pc, frs1, frs2 : frd[0..]},
       semfunc: "&RiscVZfhFdiv",
       disasm: "fdiv.h", "%frd, %frs1, %frs2";
+    fmin_h{: frs1, frs2, rm : frd, fflags},
+      resources: {next_pc, frs1, frs2 : frd[0..]},
+      semfunc: "&RiscVZfhFmin",
+      disasm: "fmin.h", "%frd, %frs1, %frs2";
+    fmax_h{: frs1, frs2, rm : frd, fflags},
+      resources: {next_pc, frs1, frs2 : frd[0..]},
+      semfunc: "&RiscVZfhFmax",
+      disasm: "fmax.h", "%frd, %frs1, %frs2";
+    fsgnj_h{: frs1, frs2 : frd },
+      resources: {next_pc, frs1, frs2 : frd[0..]},
+      semfunc: "&RiscVZfhFsgnj",
+      disasm: "fsgnj.h", "%frd, %frs1, %frs2";
+    fsgnjn_h{: frs1, frs2 : frd },
+      resources: {next_pc, frs1, frs2 : frd[0..]},
+      semfunc: "&RiscVZfhFsgnjn",
+      disasm: "fsgnjn.h", "%frd, %frs1, %frs2";
+    fsgnjnx_h{: frs1, frs2 : frd },
+      resources: {next_pc, frs1, frs2 : frd[0..]},
+      semfunc: "&RiscVZfhFsgnjx",
+      disasm: "fsgnjnx.h", "%frd, %frs1, %frs2";
   }
 }
diff --git a/riscv/riscv_zfh_instructions.cc b/riscv/riscv_zfh_instructions.cc
index 83e2342..4391530 100644
--- a/riscv/riscv_zfh_instructions.cc
+++ b/riscv/riscv_zfh_instructions.cc
@@ -402,6 +402,78 @@
       instruction, [](float a, float b) -> float { return a / b; });
 }
 
+// Take the minimum of two half precision values. Do the operation in single
+// precision.
+void RiscVZfhFmin(const Instruction *instruction) {
+  RiscVZfhBinaryHelper<HalfFP, float>(instruction,
+                                      [](float a, float b) -> float {
+                                        // On ARM std::fminf returns NaN if
+                                        // either input is NaN. Add extra checks
+                                        // to make X86 and ARM behavior the
+                                        // same.
+                                        if (std::isnan(a)) {
+                                          return b;
+                                        } else if (std::isnan(b)) {
+                                          return a;
+                                        }
+                                        return std::fminf(a, b);
+                                      });
+}
+
+// Take the maximum of two half precision values. Do the operation in single
+// precision.
+void RiscVZfhFmax(const Instruction *instruction) {
+  RiscVZfhBinaryHelper<HalfFP, float>(instruction,
+                                      [](float a, float b) -> float {
+                                        // On ARM std::fmaxf returns NaN if
+                                        // either input is NaN. Add extra checks
+                                        // to make X86 and ARM behavior the
+                                        // same.
+                                        if (std::isnan(a)) {
+                                          return b;
+                                        } else if (std::isnan(b)) {
+                                          return a;
+                                        }
+                                        return std::fmaxf(a, b);
+                                      });
+}
+
+// The result is the exponent and significand of the first source with the
+// sign bit of the second source.
+void RiscVZfhFsgnj(const Instruction *instruction) {
+  RiscVBinaryFloatNaNBoxOp<RVFpRegister::ValueType, HalfFP, HalfFP>(
+      instruction, [](HalfFP a, HalfFP b) -> HalfFP {
+        uint16_t mask =
+            FPTypeInfo<HalfFP>::kExpMask | FPTypeInfo<HalfFP>::kSigMask;
+        return HalfFP{.value = static_cast<uint16_t>((a.value & mask) |
+                                                     (b.value & ~mask))};
+      });
+}
+
+// The result is the exponent and significand of the first source with the
+// opposite sign bit of the second source.
+void RiscVZfhFsgnjn(const Instruction *instruction) {
+  RiscVBinaryFloatNaNBoxOp<RVFpRegister::ValueType, HalfFP, HalfFP>(
+      instruction, [](HalfFP a, HalfFP b) -> HalfFP {
+        uint16_t mask =
+            FPTypeInfo<HalfFP>::kExpMask | FPTypeInfo<HalfFP>::kSigMask;
+        return HalfFP{.value = static_cast<uint16_t>((a.value & mask) |
+                                                     (~b.value & ~mask))};
+      });
+}
+
+// The result is the exponent and significand of the first source with the
+// sign bit that is the exclusive or of the two source sign bits.
+void RiscVZfhFsgnjx(const Instruction *instruction) {
+  RiscVBinaryFloatNaNBoxOp<RVFpRegister::ValueType, HalfFP, HalfFP>(
+      instruction, [](HalfFP a, HalfFP b) -> HalfFP {
+        uint16_t mask =
+            FPTypeInfo<HalfFP>::kExpMask | FPTypeInfo<HalfFP>::kSigMask;
+        return HalfFP{.value = static_cast<uint16_t>(
+                          (a.value & mask) | ((a.value ^ b.value) & ~mask))};
+      });
+}
+
 // TODO(b/409778536): Factor out generic unimplemented instruction semantic
 //                    function.
 void RV32VUnimplementedInstruction(const Instruction *instruction) {
diff --git a/riscv/riscv_zfh_instructions.h b/riscv/riscv_zfh_instructions.h
index fd2f713..27a717d 100644
--- a/riscv/riscv_zfh_instructions.h
+++ b/riscv/riscv_zfh_instructions.h
@@ -134,6 +134,45 @@
 //   fflags: Accrued Exception Flags field in FCSR
 void RiscVZfhFdiv(const Instruction *instruction);
 
+// Source Operands:
+//   frs1: Float Register
+//   frs2: Float Register
+//   rm: Literal Operand (rounding mode)
+// Destination Operands:
+//   frd: Float Register
+//   fflags: Accrued Exception Flags field in FCSR
+void RiscVZfhFmin(const Instruction *instruction);
+
+// Source Operands:
+//   frs1: Float Register
+//   frs2: Float Register
+//   rm: Literal Operand (rounding mode)
+// Destination Operands:
+//   frd: Float Register
+//   fflags: Accrued Exception Flags field in FCSR
+void RiscVZfhFmax(const Instruction *instruction);
+
+// Source Operands:
+//   frs1: Float Register
+//   frs2: Float Register
+// Destination Operands:
+//   frd: Float Register
+void RiscVZfhFsgnj(const Instruction *instruction);
+
+// Source Operands:
+//   frs1: Float Register
+//   frs2: Float Register
+// Destination Operands:
+//   frd: Float Register
+void RiscVZfhFsgnjn(const Instruction *instruction);
+
+// Source Operands:
+//   frs1: Float Register
+//   frs2: Float Register
+// Destination Operands:
+//   frd: Float Register
+void RiscVZfhFsgnjx(const Instruction *instruction);
+
 }  // namespace riscv
 }  // namespace sim
 }  // namespace mpact
diff --git a/riscv/test/riscv_zfh_instructions_test.cc b/riscv/test/riscv_zfh_instructions_test.cc
index 876c5f5..7f8aa01 100644
--- a/riscv/test/riscv_zfh_instructions_test.cc
+++ b/riscv/test/riscv_zfh_instructions_test.cc
@@ -57,8 +57,13 @@
 using ::mpact::sim::riscv::RiscVZfhFadd;
 using ::mpact::sim::riscv::RiscVZfhFdiv;
 using ::mpact::sim::riscv::RiscVZfhFlhChild;
+using ::mpact::sim::riscv::RiscVZfhFmax;
+using ::mpact::sim::riscv::RiscVZfhFmin;
 using ::mpact::sim::riscv::RiscVZfhFmul;
 using ::mpact::sim::riscv::RiscVZfhFMvhx;
+using ::mpact::sim::riscv::RiscVZfhFsgnj;
+using ::mpact::sim::riscv::RiscVZfhFsgnjn;
+using ::mpact::sim::riscv::RiscVZfhFsgnjx;
 using ::mpact::sim::riscv::RiscVZfhFsub;
 using ::mpact::sim::riscv::RV32Register;
 using ::mpact::sim::riscv::RV64Register;
@@ -682,6 +687,128 @@
       });
 }
 
+// Find the minimum of two half precision values. Generate the expected result
+// by using a natively supported float datatype for the operation.
+TEST_F(RV32ZfhInstructionTest, RiscVZfhFmin) {
+  SetSemanticFunction(&RiscVZfhFmin);
+  BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>(
+      "fmin.h", instruction_, {"f", "f", "f"}, 32,
+      [this](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> {
+        FPRoundingMode rm = rv_fp_->GetRoundingMode();
+        uint32_t fflags = 0;
+        double a_f =
+            FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags);
+        double b_f =
+            FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags);
+        double min_f = 0;
+        if (a_f == 0 && b_f == 0 && (a.value != b.value)) {
+          // Special case for -0 vs +0.
+          min_f = absl::bit_cast<double>(FPTypeInfo<double>::kNegZero);
+        } else if (std::isnan(a_f) && !std::isnan(b_f)) {
+          min_f = b_f;  // pick the non-NaN value
+        } else if (!std::isnan(a_f) && std::isnan(b_f)) {
+          min_f = a_f;  // pick the non-NaN value
+        } else if (std::isnan(a_f) && std::isnan(b_f)) {
+          min_f = absl::bit_cast<double>(FPTypeInfo<double>::kCanonicalNaN);
+        } else if (std::isinf(a_f) && !std::isinf(b_f)) {
+          min_f = a_f < 0 ? a_f : b_f;  // min(+/-inf, x)
+        } else if (!std::isinf(a_f) && std::isinf(b_f)) {
+          min_f = b_f < 0 ? b_f : a_f;  // min(x, +/-inf)
+        } else {
+          if (a_f < b_f) {
+            min_f = a_f;
+          } else {
+            min_f = b_f;
+          }
+        }
+        HalfFP result =
+            FpConversionsTestHelper(min_f).ConvertWithFlags<HalfFP>(fflags, rm);
+        return std::make_tuple(result, fflags);
+      });
+}
+
+// Find the maximum of two half precision values. Generate the expected result
+// by using a natively supported float datatype for the operation.
+TEST_F(RV32ZfhInstructionTest, RiscVZfhFmax) {
+  SetSemanticFunction(&RiscVZfhFmax);
+  BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>(
+      "fmax.h", instruction_, {"f", "f", "f"}, 32,
+      [this](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> {
+        FPRoundingMode rm = rv_fp_->GetRoundingMode();
+        uint32_t fflags = 0;
+        double a_f =
+            FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags);
+        double b_f =
+            FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags);
+        double max_f = 0;
+        if (a_f == 0 && b_f == 0 && (a.value != b.value)) {
+          // Special case for -0 vs +0.
+          max_f = absl::bit_cast<double>(FPTypeInfo<double>::kPosZero);
+        } else if (std::isnan(a_f) && !std::isnan(b_f)) {
+          max_f = b_f;  // pick the non-NaN value
+        } else if (!std::isnan(a_f) && std::isnan(b_f)) {
+          max_f = a_f;  // pick the non-NaN value
+        } else if (std::isnan(a_f) && std::isnan(b_f)) {
+          max_f = absl::bit_cast<double>(FPTypeInfo<double>::kCanonicalNaN);
+        } else if (std::isinf(a_f) && !std::isinf(b_f)) {
+          max_f = a_f > 0 ? a_f : b_f;  // max(+/-inf, x)
+        } else if (!std::isinf(a_f) && std::isinf(b_f)) {
+          max_f = b_f > 0 ? b_f : a_f;  // max(x, +/-inf)
+        } else {
+          if (a_f > b_f) {
+            max_f = a_f;
+          } else {
+            max_f = b_f;
+          }
+        }
+        HalfFP result =
+            FpConversionsTestHelper(max_f).ConvertWithFlags<HalfFP>(fflags, rm);
+        return std::make_tuple(result, fflags);
+      });
+}
+
+TEST_F(RV32ZfhInstructionTest, RiscVZfhFsgnj) {
+  SetSemanticFunction(&RiscVZfhFsgnj);
+  BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>(
+      "fsgnj.h", instruction_, {"f", "f", "f"}, 32,
+      [](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> {
+        HalfFP result{.value = 0};
+        result.value |= a.value & (FPTypeInfo<HalfFP>::kExpMask |
+                                   FPTypeInfo<HalfFP>::kSigMask);
+        result.value |= b.value & ~(FPTypeInfo<HalfFP>::kExpMask |
+                                    FPTypeInfo<HalfFP>::kSigMask);
+        return std::make_tuple(result, 0);
+      });
+}
+
+TEST_F(RV32ZfhInstructionTest, RiscVZfhFsgnjn) {
+  SetSemanticFunction(&RiscVZfhFsgnjn);
+  BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>(
+      "fsgnjn.h", instruction_, {"f", "f", "f"}, 32,
+      [](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> {
+        HalfFP result{.value = 0};
+        result.value |= a.value & (FPTypeInfo<HalfFP>::kExpMask |
+                                   FPTypeInfo<HalfFP>::kSigMask);
+        result.value |= (~b.value) & ~(FPTypeInfo<HalfFP>::kExpMask |
+                                       FPTypeInfo<HalfFP>::kSigMask);
+        return std::make_tuple(result, 0);
+      });
+}
+
+TEST_F(RV32ZfhInstructionTest, RiscVZfhFsgnjx) {
+  SetSemanticFunction(&RiscVZfhFsgnjx);
+  BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>(
+      "fsgnjn.h", instruction_, {"f", "f", "f"}, 32,
+      [](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> {
+        HalfFP result{.value = 0};
+        result.value |= a.value & (FPTypeInfo<HalfFP>::kExpMask |
+                                   FPTypeInfo<HalfFP>::kSigMask);
+        result.value |= (a.value ^ b.value) & ~(FPTypeInfo<HalfFP>::kExpMask |
+                                                FPTypeInfo<HalfFP>::kSigMask);
+        return std::make_tuple(result, 0);
+      });
+}
+
 class RV64ZfhInstructionTest : public RVZfhInstructionTestBase {};
 
 // Move half precision from a float register to an integer register. The IEEE754