[llvm] [Mips] Add r5900 (PlayStation 2 Emotion Engine) FPU Support (PR #178942)

Rick Gaiser via llvm-commits llvm-commits at lists.llvm.org
Sun Feb 1 03:59:43 PST 2026


https://github.com/rickgaiser updated https://github.com/llvm/llvm-project/pull/178942

>From ce13ddea7bc75fe2058c6fb8e49c600a5e5b1742 Mon Sep 17 00:00:00 2001
From: Rick Gaiser <rgaiser at gmail.com>
Date: Wed, 28 Jan 2026 20:48:27 +0100
Subject: [PATCH 1/4] [MIPS] Add infrastructure for single-precision-only FPU
 support

Add support for single-precision-only FPU configurations by:
- Adding isSingleFloat() method to MipsAsmParser
- Adding SINGLE FpABIKind to MipsABIFlagsSection
- Properly setting CPR1Size and FpABI for single-float mode
- Excluding double-precision PseudoCVT instructions when using single-float

This infrastructure allows MIPS targets with single-precision-only
FPUs to properly declare their capabilities and avoid generating
double-precision instructions.
---
 .../Target/Mips/AsmParser/MipsAsmParser.cpp   |  5 ++++
 .../Mips/MCTargetDesc/MipsABIFlagsSection.cpp |  2 ++
 .../Mips/MCTargetDesc/MipsABIFlagsSection.h   |  6 ++++-
 llvm/lib/Target/Mips/MipsInstrFPU.td          | 26 ++++++++++++-------
 4 files changed, 28 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/Target/Mips/AsmParser/MipsAsmParser.cpp b/llvm/lib/Target/Mips/AsmParser/MipsAsmParser.cpp
index f91c378ad0afa..67b6622d0ab87 100644
--- a/llvm/lib/Target/Mips/AsmParser/MipsAsmParser.cpp
+++ b/llvm/lib/Target/Mips/AsmParser/MipsAsmParser.cpp
@@ -710,6 +710,11 @@ class MipsAsmParser : public MCTargetAsmParser {
   bool useSoftFloat() const {
     return getSTI().hasFeature(Mips::FeatureSoftFloat);
   }
+
+  bool isSingleFloat() const {
+    return getSTI().hasFeature(Mips::FeatureSingleFloat);
+  }
+
   bool hasMT() const {
     return getSTI().hasFeature(Mips::FeatureMT);
   }
diff --git a/llvm/lib/Target/Mips/MCTargetDesc/MipsABIFlagsSection.cpp b/llvm/lib/Target/Mips/MCTargetDesc/MipsABIFlagsSection.cpp
index c5a1a3e6286ed..9da7c8fbba7ac 100644
--- a/llvm/lib/Target/Mips/MCTargetDesc/MipsABIFlagsSection.cpp
+++ b/llvm/lib/Target/Mips/MCTargetDesc/MipsABIFlagsSection.cpp
@@ -29,6 +29,8 @@ uint8_t MipsABIFlagsSection::getFpABIValue() {
       return OddSPReg ? Mips::Val_GNU_MIPS_ABI_FP_64
                       : Mips::Val_GNU_MIPS_ABI_FP_64A;
     return Mips::Val_GNU_MIPS_ABI_FP_DOUBLE;
+  case FpABIKind::SINGLE:
+    return Mips::Val_GNU_MIPS_ABI_FP_SINGLE;
   }
 
   llvm_unreachable("unexpected fp abi value");
diff --git a/llvm/lib/Target/Mips/MCTargetDesc/MipsABIFlagsSection.h b/llvm/lib/Target/Mips/MCTargetDesc/MipsABIFlagsSection.h
index f7b2fa5537d31..53fda3e9efbcb 100644
--- a/llvm/lib/Target/Mips/MCTargetDesc/MipsABIFlagsSection.h
+++ b/llvm/lib/Target/Mips/MCTargetDesc/MipsABIFlagsSection.h
@@ -20,7 +20,7 @@ class StringRef;
 
 struct MipsABIFlagsSection {
   // Internal representation of the fp_abi related values used in .module.
-  enum class FpABIKind { ANY, XX, S32, S64, SOFT };
+  enum class FpABIKind { ANY, XX, S32, S64, SOFT, SINGLE };
 
   // Version of flags structure.
   uint16_t Version = 0;
@@ -133,6 +133,8 @@ struct MipsABIFlagsSection {
       CPR1Size = Mips::AFL_REG_NONE;
     else if (P.hasMSA())
       CPR1Size = Mips::AFL_REG_128;
+    else if (P.isSingleFloat())
+      CPR1Size = Mips::AFL_REG_32;
     else
       CPR1Size = P.isFP64bit() ? Mips::AFL_REG_64 : Mips::AFL_REG_32;
   }
@@ -179,6 +181,8 @@ struct MipsABIFlagsSection {
     FpABI = FpABIKind::ANY;
     if (P.useSoftFloat())
       FpABI = FpABIKind::SOFT;
+    else if (P.isSingleFloat())
+      FpABI = FpABIKind::SINGLE;
     else if (P.isABI_N32() || P.isABI_N64())
       FpABI = FpABIKind::S64;
     else if (P.isABI_O32()) {
diff --git a/llvm/lib/Target/Mips/MipsInstrFPU.td b/llvm/lib/Target/Mips/MipsInstrFPU.td
index 50d7a74ada60e..703c1edda6c78 100644
--- a/llvm/lib/Target/Mips/MipsInstrFPU.td
+++ b/llvm/lib/Target/Mips/MipsInstrFPU.td
@@ -89,8 +89,8 @@ def HasMips3D        : Predicate<"Subtarget->has3D()">,
 // They are mutually exclusive.
 //===----------------------------------------------------------------------===//
 
-class FGR_32 { list<Predicate> FGRPredicates = [NotFP64bit]; }
-class FGR_64 { list<Predicate> FGRPredicates = [IsFP64bit]; }
+class FGR_32 { list<Predicate> FGRPredicates = [NotFP64bit, IsNotSingleFloat]; }
+class FGR_64 { list<Predicate> FGRPredicates = [IsFP64bit, IsNotSingleFloat]; }
 class HARDFLOAT { list<Predicate> HardFloatPredicate = [IsNotSoftFloat]; }
 
 //===----------------------------------------------------------------------===//
@@ -541,10 +541,12 @@ let DecoderNamespace = "MipsFP64", mayRaiseFPException = 1, Uses = [FCR31] in {
 
 let isPseudo = 1, isCodeGenOnly = 1 in {
   def PseudoCVT_S_W : ABSS_FT<"", FGR32Opnd, GPR32Opnd, II_CVT>;
-  def PseudoCVT_D32_W : ABSS_FT<"", AFGR64Opnd, GPR32Opnd, II_CVT>;
-  def PseudoCVT_S_L : ABSS_FT<"", FGR64Opnd, GPR64Opnd, II_CVT>;
-  def PseudoCVT_D64_W : ABSS_FT<"", FGR64Opnd, GPR32Opnd, II_CVT>;
-  def PseudoCVT_D64_L : ABSS_FT<"", FGR64Opnd, GPR64Opnd, II_CVT>;
+  let Predicates = [IsNotSingleFloat] in {
+    def PseudoCVT_D32_W : ABSS_FT<"", AFGR64Opnd, GPR32Opnd, II_CVT>;
+    def PseudoCVT_S_L : ABSS_FT<"", FGR64Opnd, GPR64Opnd, II_CVT>;
+    def PseudoCVT_D64_W : ABSS_FT<"", FGR64Opnd, GPR32Opnd, II_CVT>;
+    def PseudoCVT_D64_L : ABSS_FT<"", FGR64Opnd, GPR64Opnd, II_CVT>;
+  }
 }
 
 let AdditionalPredicates = [NotInMicroMips, UseAbs] in {
@@ -605,10 +607,14 @@ let AdditionalPredicates = [NotInMicroMips] in {
     let DecoderNamespace = "MipsFP64";
   }
 
-  def DMTC1 : MTC1_FT<"dmtc1", FGR64Opnd, GPR64Opnd, II_DMTC1,
-              bitconvert>, MFC1_FM<5>, ISA_MIPS3;
-  def DMFC1 : MFC1_FT<"dmfc1", GPR64Opnd, FGR64Opnd, II_DMFC1,
-                      bitconvert>, MFC1_FM<1>, ISA_MIPS3;
+  let AdditionalPredicates = [IsNotSingleFloat] in {
+    def DMTC1 : MTC1_FT<"dmtc1", FGR64Opnd, GPR64Opnd, II_DMTC1, bitconvert>,
+                MFC1_FM<5>,
+                ISA_MIPS3;
+    def DMFC1 : MFC1_FT<"dmfc1", GPR64Opnd, FGR64Opnd, II_DMFC1, bitconvert>,
+                MFC1_FM<1>,
+                ISA_MIPS3;
+  }
   let isMoveReg = 1 in {
     def FMOV_S   : MMRel, ABSS_FT<"mov.s", FGR32Opnd, FGR32Opnd, II_MOV_S>,
                    ABSS_FM<0x6, 16>, ISA_MIPS1;

>From 13032c4d55b26effe49d55ca1f41f698a07b3ec3 Mon Sep 17 00:00:00 2001
From: Rick Gaiser <rgaiser at gmail.com>
Date: Fri, 30 Jan 2026 17:20:47 +0100
Subject: [PATCH 2/4] [MIPS] Add R5900 FPU support

Enable FPU support for the R5900 (PS2 Emotion Engine) by:
- Switching R5900 from soft-float to single-float mode
- Implementing R5900-specific FPU lowering with limited compare conditions
  (R5900 only supports C.F, C.EQ, C.LT, C.LE)
- Using CVT.W.S instead of TRUNC.W.S for FP-to-int conversion
- Renaming HasR5900 predicate to IsR5900 for consistency
- Adding test coverage for R5900 FPU instructions and comparisons

The R5900 has a single-precision-only FPU with limited functionality
compared to standard MIPS FPUs.
---
 llvm/lib/Target/Mips/Mips.td              |   2 +-
 llvm/lib/Target/Mips/MipsISelLowering.cpp |  70 ++++++++++
 llvm/lib/Target/Mips/MipsInstrFPU.td      |   4 +
 llvm/lib/Target/Mips/MipsInstrInfo.td     |   4 +-
 llvm/test/CodeGen/Mips/fcmp.ll            |  46 +++++++
 llvm/test/MC/Mips/r5900-fpu.s             | 150 ++++++++++++++++++++++
 6 files changed, 273 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/MC/Mips/r5900-fpu.s

diff --git a/llvm/lib/Target/Mips/Mips.td b/llvm/lib/Target/Mips/Mips.td
index 2f3ec5d051a79..8176c63d8660c 100644
--- a/llvm/lib/Target/Mips/Mips.td
+++ b/llvm/lib/Target/Mips/Mips.td
@@ -191,7 +191,7 @@ def FeatureFixR5900 : SubtargetFeature<"fix-r5900", "FixR5900", "true",
 
 def FeatureR5900 : SubtargetFeature<"r5900", "IsR5900", "true",
                                     "R5900 (PS2 Emotion Engine) Support",
-                                    [FeatureMips3, FeatureSoftFloat,
+                                    [FeatureMips3, FeatureSingleFloat,
                                      FeatureFixR5900]>;
 
 def FeatureUseTCCInDIV : SubtargetFeature<
diff --git a/llvm/lib/Target/Mips/MipsISelLowering.cpp b/llvm/lib/Target/Mips/MipsISelLowering.cpp
index f7e2825edcc5d..b8fde7e03cae5 100644
--- a/llvm/lib/Target/Mips/MipsISelLowering.cpp
+++ b/llvm/lib/Target/Mips/MipsISelLowering.cpp
@@ -539,6 +539,65 @@ static Mips::CondCode condCodeToFCC(ISD::CondCode CC) {
   }
 }
 
+// R5900 FPU only supports 4 compare conditions: C.F, C.EQ, C.LT, C.LE
+// (opcodes 0x30, 0x32, 0x34, 0x36). Map unsupported conditions to equivalents.
+// The R5900 FPU doesn't produce NaN, so unordered comparisons can be mapped
+// to their ordered equivalents.
+// Returns the R5900-compatible condition code and whether operands should be
+// swapped.
+static std::pair<Mips::CondCode, bool> condCodeToFCCR5900(ISD::CondCode CC) {
+  // R5900 supports: FCOND_F (0), FCOND_OEQ (2), FCOND_OLT (4), FCOND_OLE (6)
+  // All other conditions must be transformed.
+  switch (CC) {
+  default:
+    llvm_unreachable("Unknown fp condition code!");
+  // Directly supported conditions
+  case ISD::SETEQ:
+  case ISD::SETOEQ:
+    return {Mips::FCOND_OEQ, false};
+  case ISD::SETLT:
+  case ISD::SETOLT:
+    return {Mips::FCOND_OLT, false};
+  case ISD::SETLE:
+  case ISD::SETOLE:
+    return {Mips::FCOND_OLE, false};
+
+  // Unordered variants - map to ordered (R5900 has no NaN)
+  case ISD::SETUEQ:
+    return {Mips::FCOND_OEQ, false};
+  case ISD::SETULT:
+    return {Mips::FCOND_OLT, false};
+  case ISD::SETULE:
+    return {Mips::FCOND_OLE, false};
+
+  // Greater-than: use less-than with swapped operands
+  case ISD::SETGT:
+  case ISD::SETOGT:
+    return {Mips::FCOND_OLT, true}; // a > b  ->  b < a
+  case ISD::SETGE:
+  case ISD::SETOGE:
+    return {Mips::FCOND_OLE, true}; // a >= b  ->  b <= a
+  case ISD::SETUGT:
+    return {Mips::FCOND_OLT, true}; // a > b unordered  ->  b < a (no NaN)
+  case ISD::SETUGE:
+    return {Mips::FCOND_OLE, true}; // a >= b unordered  ->  b <= a (no NaN)
+
+  // Not-equal: use EQ and invert result
+  case ISD::SETNE:
+  case ISD::SETONE:
+    return {Mips::FCOND_UNE, false};
+  case ISD::SETUNE:
+    return {Mips::FCOND_UNE, false};
+
+  // Unordered: always false on R5900 (no NaN)
+  case ISD::SETUO:
+    return {Mips::FCOND_F, false};
+  // Ordered: always true (use FCOND_T which is inverted F)
+  case ISD::SETO:
+    return {Mips::FCOND_OR, false};
+  }
+}
+
 /// This function returns true if the floating point conditional branches and
 /// conditional moves which use condition code CC should be inverted.
 static bool invertFPCondCodeUser(Mips::CondCode CC) {
@@ -571,6 +630,17 @@ static SDValue createFPCmp(SelectionDAG &DAG, const SDValue &Op) {
   // node if necessary.
   ISD::CondCode CC = cast<CondCodeSDNode>(Op.getOperand(2))->get();
 
+  // R5900 FPU only supports C.F, C.EQ, C.LT, C.LE (conditions 0, 2, 4, 6).
+  // Use R5900-specific condition code mapping which may swap operands.
+  const MipsSubtarget &Subtarget = DAG.getSubtarget<MipsSubtarget>();
+  if (Subtarget.isR5900()) {
+    auto [FCC, Swap] = condCodeToFCCR5900(CC);
+    if (Swap)
+      std::swap(LHS, RHS);
+    return DAG.getNode(MipsISD::FPCmp, DL, MVT::Glue, LHS, RHS,
+                       DAG.getConstant(FCC, DL, MVT::i32));
+  }
+
   return DAG.getNode(MipsISD::FPCmp, DL, MVT::Glue, LHS, RHS,
                      DAG.getConstant(condCodeToFCC(CC), DL, MVT::i32));
 }
diff --git a/llvm/lib/Target/Mips/MipsInstrFPU.td b/llvm/lib/Target/Mips/MipsInstrFPU.td
index 703c1edda6c78..da0887cdcc232 100644
--- a/llvm/lib/Target/Mips/MipsInstrFPU.td
+++ b/llvm/lib/Target/Mips/MipsInstrFPU.td
@@ -965,6 +965,10 @@ def : MipsPat<(f32 fpimm0neg), (FNEG_S (MTC1 ZERO))>, ISA_MIPS1;
 
 def : MipsPat<(f32 (any_sint_to_fp GPR32Opnd:$src)),
               (PseudoCVT_S_W GPR32Opnd:$src)>;
+let Predicates = [IsR5900] in {
+  def : MipsPat<(MipsTruncIntFP FGR32Opnd:$src),
+                (CVT_W_S FGR32Opnd:$src)>, ISA_MIPS1;
+}
 def : MipsPat<(MipsTruncIntFP FGR32Opnd:$src),
               (TRUNC_W_S FGR32Opnd:$src)>, ISA_MIPS1;
 
diff --git a/llvm/lib/Target/Mips/MipsInstrInfo.td b/llvm/lib/Target/Mips/MipsInstrInfo.td
index 2a476e789032c..8127ec5c043f2 100644
--- a/llvm/lib/Target/Mips/MipsInstrInfo.td
+++ b/llvm/lib/Target/Mips/MipsInstrInfo.td
@@ -259,8 +259,8 @@ def UseIndirectJumpsHazard : Predicate<"Subtarget->useIndirectJumpsHazard()">,
                             AssemblerPredicate<(all_of FeatureUseIndirectJumpsHazard)>;
 def NoIndirectJumpGuards : Predicate<"!Subtarget->useIndirectJumpsHazard()">,
                            AssemblerPredicate<(all_of (not FeatureUseIndirectJumpsHazard))>;
-def HasR5900 : Predicate<"Subtarget->isR5900()">,
-               AssemblerPredicate<(all_of FeatureR5900)>;
+def IsR5900 : Predicate<"Subtarget->isR5900()">,
+              AssemblerPredicate<(all_of FeatureR5900)>;
 def NotR5900 : Predicate<"!Subtarget->isR5900()">,
                AssemblerPredicate<(all_of (not FeatureR5900))>;
 def HasCRC   : Predicate<"Subtarget->hasCRC()">,
diff --git a/llvm/test/CodeGen/Mips/fcmp.ll b/llvm/test/CodeGen/Mips/fcmp.ll
index 00c2a10fc226d..c3f062e5a30cf 100644
--- a/llvm/test/CodeGen/Mips/fcmp.ll
+++ b/llvm/test/CodeGen/Mips/fcmp.ll
@@ -16,6 +16,8 @@
 ; RUN:    -check-prefixes=ALL,MM,MM32R3
 ; RUN: llc < %s -mtriple=mips -mcpu=mips32r6 -mattr=+micromips | FileCheck %s \
 ; RUN:    -check-prefixes=ALL,MM,MMR6,MM32R6
+; RUN: llc < %s -mtriple=mips64el -mcpu=r5900 | \
+; RUN:    FileCheck %s -check-prefixes=ALL,R5900
 
 define i32 @false_f32(float %a, float %b) nounwind {
 ; ALL-LABEL: false_f32:
@@ -27,6 +29,8 @@ define i32 @false_f32(float %a, float %b) nounwind {
 
 ; 64-CMP:        addiu $2, $zero, 0
 
+; R5900:         addiu $2, $zero, 0
+
 ; MM-DAG:        li16 $2, 0
 
   %1 = fcmp false float %a, %b
@@ -45,6 +49,9 @@ define i32 @oeq_f32(float %a, float %b) nounwind {
 ; 64-C-DAG:      c.eq.s $f12, $f13
 ; 64-C:          movf $2, $zero, $fcc0
 
+; R5900:         c.eq.s $f12, $f13
+; R5900:         bc1f
+
 ; 32-CMP-DAG:    cmp.eq.s $[[T0:f[0-9]+]], $f12, $f14
 ; 32-CMP-DAG:    mfc1 $[[T1:[0-9]+]], $[[T0]]
 ; 32-CMP-DAG:    andi $2, $[[T1]], 1
@@ -78,6 +85,9 @@ define i32 @ogt_f32(float %a, float %b) nounwind {
 ; 64-C-DAG:      c.ule.s $f12, $f13
 ; 64-C:          movt $2, $zero, $fcc0
 
+; R5900:         c.olt.s $f13, $f12
+; R5900:         bc1f
+
 ; 32-CMP-DAG:    cmp.lt.s $[[T0:f[0-9]+]], $f14, $f12
 ; 32-CMP-DAG:    mfc1 $[[T1:[0-9]+]], $[[T0]]
 ; 32-CMP-DAG:    andi $2, $[[T1]], 1
@@ -111,6 +121,9 @@ define i32 @oge_f32(float %a, float %b) nounwind {
 ; 64-C-DAG:      c.ult.s $f12, $f13
 ; 64-C:          movt $2, $zero, $fcc0
 
+; R5900:         c.ole.s $f13, $f12
+; R5900:         bc1f
+
 ; 32-CMP-DAG:    cmp.le.s $[[T0:f[0-9]+]], $f14, $f12
 ; 32-CMP-DAG:    mfc1 $[[T1:[0-9]+]], $[[T0]]
 ; 32-CMP-DAG:    andi $2, $[[T1]], 1
@@ -144,6 +157,9 @@ define i32 @olt_f32(float %a, float %b) nounwind {
 ; 64-C-DAG:      c.olt.s $f12, $f13
 ; 64-C:          movf $2, $zero, $fcc0
 
+; R5900:         c.olt.s $f12, $f13
+; R5900:         bc1f
+
 ; 32-CMP-DAG:    cmp.lt.s $[[T0:f[0-9]+]], $f12, $f14
 ; 32-CMP-DAG:    mfc1 $[[T1:[0-9]+]], $[[T0]]
 ; 32-CMP-DAG:    andi $2, $[[T1]], 1
@@ -177,6 +193,9 @@ define i32 @ole_f32(float %a, float %b) nounwind {
 ; 64-C-DAG:      c.ole.s $f12, $f13
 ; 64-C:          movf $2, $zero, $fcc0
 
+; R5900:         c.ole.s $f12, $f13
+; R5900:         bc1f
+
 ; 32-CMP-DAG:    cmp.le.s $[[T0:f[0-9]+]], $f12, $f14
 ; 32-CMP-DAG:    mfc1 $[[T1:[0-9]+]], $[[T0]]
 ; 32-CMP-DAG:    andi $2, $[[T1]], 1
@@ -210,6 +229,9 @@ define i32 @one_f32(float %a, float %b) nounwind {
 ; 64-C-DAG:      c.ueq.s $f12, $f13
 ; 64-C:          movt $2, $zero, $fcc0
 
+; R5900:         c.eq.s $f12, $f13
+; R5900:         bc1t
+
 ; 32-CMP-DAG:    cmp.ueq.s $[[T0:f[0-9]+]], $f12, $f14
 ; 32-CMP-DAG:    mfc1 $[[T1:[0-9]+]], $[[T0]]
 ; 32-CMP-DAG:    not $[[T2:[0-9]+]], $[[T1]]
@@ -246,6 +268,8 @@ define i32 @ord_f32(float %a, float %b) nounwind {
 ; 64-C-DAG:      c.un.s $f12, $f13
 ; 64-C:          movt $2, $zero, $fcc0
 
+; R5900:         addiu $2, $zero, 1
+
 ; 32-CMP-DAG:    cmp.un.s $[[T0:f[0-9]+]], $f12, $f14
 ; 32-CMP-DAG:    mfc1 $[[T1:[0-9]+]], $[[T0]]
 ; 32-CMP-DAG:    not $[[T2:[0-9]+]], $[[T1]]
@@ -282,6 +306,9 @@ define i32 @ueq_f32(float %a, float %b) nounwind {
 ; 64-C-DAG:      c.ueq.s $f12, $f13
 ; 64-C:          movf $2, $zero, $fcc0
 
+; R5900:         c.eq.s $f12, $f13
+; R5900:         bc1f
+
 ; 32-CMP-DAG:    cmp.ueq.s $[[T0:f[0-9]+]], $f12, $f14
 ; 32-CMP-DAG:    mfc1 $[[T1:[0-9]+]], $[[T0]]
 ; 32-CMP-DAG:    andi $2, $[[T1]], 1
@@ -315,6 +342,9 @@ define i32 @ugt_f32(float %a, float %b) nounwind {
 ; 64-C-DAG:      c.ole.s $f12, $f13
 ; 64-C:          movt $2, $zero, $fcc0
 
+; R5900:         c.olt.s $f13, $f12
+; R5900:         bc1f
+
 ; 32-CMP-DAG:    cmp.ult.s $[[T0:f[0-9]+]], $f14, $f12
 ; 32-CMP-DAG:    mfc1 $[[T1:[0-9]+]], $[[T0]]
 ; 32-CMP-DAG:    andi $2, $[[T1]], 1
@@ -348,6 +378,9 @@ define i32 @uge_f32(float %a, float %b) nounwind {
 ; 64-C-DAG:      c.olt.s $f12, $f13
 ; 64-C:          movt $2, $zero, $fcc0
 
+; R5900:         c.ole.s $f13, $f12
+; R5900:         bc1f
+
 ; 32-CMP-DAG:    cmp.ule.s $[[T0:f[0-9]+]], $f14, $f12
 ; 32-CMP-DAG:    mfc1 $[[T1:[0-9]+]], $[[T0]]
 ; 32-CMP-DAG:    andi $2, $[[T1]], 1
@@ -381,6 +414,9 @@ define i32 @ult_f32(float %a, float %b) nounwind {
 ; 64-C-DAG:      c.ult.s $f12, $f13
 ; 64-C:          movf $2, $zero, $fcc0
 
+; R5900:         c.olt.s $f12, $f13
+; R5900:         bc1f
+
 ; 32-CMP-DAG:    cmp.ult.s $[[T0:f[0-9]+]], $f12, $f14
 ; 32-CMP-DAG:    mfc1 $[[T1:[0-9]+]], $[[T0]]
 ; 32-CMP-DAG:    andi $2, $[[T1]], 1
@@ -414,6 +450,9 @@ define i32 @ule_f32(float %a, float %b) nounwind {
 ; 64-C-DAG:      c.ule.s $f12, $f13
 ; 64-C:          movf $2, $zero, $fcc0
 
+; R5900:         c.ole.s $f12, $f13
+; R5900:         bc1f
+
 ; 32-CMP-DAG:    cmp.ule.s $[[T0:f[0-9]+]], $f12, $f14
 ; 32-CMP-DAG:    mfc1 $[[T1:[0-9]+]], $[[T0]]
 ; 32-CMP-DAG:    andi $2, $[[T1]], 1
@@ -447,6 +486,9 @@ define i32 @une_f32(float %a, float %b) nounwind {
 ; 64-C-DAG:      c.eq.s $f12, $f13
 ; 64-C:          movt $2, $zero, $fcc0
 
+; R5900:         c.eq.s $f12, $f13
+; R5900:         bc1t
+
 ; 32-CMP-DAG:    cmp.eq.s $[[T0:f[0-9]+]], $f12, $f14
 ; 32-CMP-DAG:    mfc1 $[[T1:[0-9]+]], $[[T0]]
 ; 32-CMP-DAG:    not $[[T2:[0-9]+]], $[[T1]]
@@ -483,6 +525,8 @@ define i32 @uno_f32(float %a, float %b) nounwind {
 ; 64-C-DAG:      c.un.s $f12, $f13
 ; 64-C:          movf $2, $zero, $fcc0
 
+; R5900:         addiu $2, $zero, 0
+
 ; 32-CMP-DAG:    cmp.un.s $[[T0:f[0-9]+]], $f12, $f14
 ; 32-CMP-DAG:    mfc1 $[[T1:[0-9]+]], $[[T0]]
 ; 32-CMP-DAG:    andi $2, $[[T1]], 1
@@ -515,6 +559,8 @@ define i32 @true_f32(float %a, float %b) nounwind {
 
 ; 64-CMP:        addiu $2, $zero, 1
 
+; R5900:         addiu $2, $zero, 1
+
 ; MM-DAG:        li16 $2, 1
 
   %1 = fcmp true float %a, %b
diff --git a/llvm/test/MC/Mips/r5900-fpu.s b/llvm/test/MC/Mips/r5900-fpu.s
new file mode 100644
index 0000000000000..eafdbad3a3845
--- /dev/null
+++ b/llvm/test/MC/Mips/r5900-fpu.s
@@ -0,0 +1,150 @@
+# RUN: llvm-mc %s -triple=mips64el-unknown-linux -mcpu=r5900 -show-encoding \
+# RUN:   | FileCheck %s
+
+# Test R5900 FPU instructions (single-precision only, no double-precision support)
+# The R5900 has 32x32-bit FPU registers and a 32-bit accumulator (ACC)
+
+#------------------------------------------------------------------------------
+# FP Arithmetic Instructions
+#------------------------------------------------------------------------------
+
+# CHECK: abs.s   $f6, $f7           # encoding: [0x85,0x39,0x00,0x46]
+abs.s   $f6, $f7
+
+# CHECK: add.s   $f9, $f6, $f7      # encoding: [0x40,0x32,0x07,0x46]
+add.s   $f9, $f6, $f7
+
+# CHECK: sub.s   $f9, $f6, $f7      # encoding: [0x41,0x32,0x07,0x46]
+sub.s   $f9, $f6, $f7
+
+# CHECK: mul.s   $f9, $f6, $f7      # encoding: [0x42,0x32,0x07,0x46]
+mul.s   $f9, $f6, $f7
+
+# CHECK: div.s   $f9, $f6, $f7      # encoding: [0x43,0x32,0x07,0x46]
+div.s   $f9, $f6, $f7
+
+# CHECK: sqrt.s  $f6, $f7           # encoding: [0x84,0x39,0x00,0x46]
+sqrt.s  $f6, $f7
+
+# CHECK: neg.s   $f6, $f7           # encoding: [0x87,0x39,0x00,0x46]
+neg.s   $f6, $f7
+
+# CHECK: mov.s   $f6, $f7           # encoding: [0x86,0x39,0x00,0x46]
+mov.s   $f6, $f7
+
+#------------------------------------------------------------------------------
+# FP Conversion Instructions
+#------------------------------------------------------------------------------
+
+# CHECK: cvt.s.w $f6, $f7           # encoding: [0xa0,0x39,0x80,0x46]
+cvt.s.w $f6, $f7
+
+# CHECK: cvt.w.s $f6, $f7           # encoding: [0xa4,0x39,0x00,0x46]
+cvt.w.s $f6, $f7
+
+#------------------------------------------------------------------------------
+# FP Compare Instructions (R5900 supports only 4 comparisons: F, EQ, OLT, OLE)
+#------------------------------------------------------------------------------
+
+# CHECK: c.f.s   $f6, $f7           # encoding: [0x30,0x30,0x07,0x46]
+c.f.s   $f6, $f7
+
+# CHECK: c.eq.s  $f6, $f7           # encoding: [0x32,0x30,0x07,0x46]
+c.eq.s  $f6, $f7
+
+# CHECK: c.olt.s $f6, $f7           # encoding: [0x34,0x30,0x07,0x46]
+c.olt.s $f6, $f7
+
+# CHECK: c.ole.s $f6, $f7           # encoding: [0x36,0x30,0x07,0x46]
+c.ole.s $f6, $f7
+
+#------------------------------------------------------------------------------
+# FP Branch Instructions
+#------------------------------------------------------------------------------
+
+# CHECK: bc1f    8                  # encoding: [0x02,0x00,0x00,0x45]
+bc1f    8
+
+# CHECK: bc1fl   8                  # encoding: [0x02,0x00,0x02,0x45]
+bc1fl   8
+
+# CHECK: bc1t    8                  # encoding: [0x02,0x00,0x01,0x45]
+bc1t    8
+
+# CHECK: bc1tl   8                  # encoding: [0x02,0x00,0x03,0x45]
+bc1tl   8
+
+#------------------------------------------------------------------------------
+# FP Load/Store Instructions
+#------------------------------------------------------------------------------
+
+# CHECK: lwc1    $f6, 4($5)         # encoding: [0x04,0x00,0xa6,0xc4]
+lwc1    $f6, 4($5)
+
+# CHECK: swc1    $f6, 4($5)         # encoding: [0x04,0x00,0xa6,0xe4]
+swc1    $f6, 4($5)
+
+#------------------------------------------------------------------------------
+# FP Move Instructions (between GPR and FPR)
+#------------------------------------------------------------------------------
+
+# CHECK: mfc1    $6, $f7            # encoding: [0x00,0x38,0x06,0x44]
+mfc1    $6, $f7
+
+# CHECK: mtc1    $6, $f7            # encoding: [0x00,0x38,0x86,0x44]
+mtc1    $6, $f7
+
+#------------------------------------------------------------------------------
+# FP Control Register Move Instructions
+#------------------------------------------------------------------------------
+
+# CHECK: cfc1    $6, $0             # encoding: [0x00,0x00,0x46,0x44]
+cfc1    $6, $0
+
+# CHECK: ctc1    $6, $31            # encoding: [0x00,0xf8,0xc6,0x44]
+ctc1    $6, $31
+
+# TODO: R5900 FP Accumulator Instructions
+# #------------------------------------------------------------------------------
+# # R5900-Specific: FP Accumulator Instructions
+# #------------------------------------------------------------------------------
+#
+# # DISABLED: adda.s  $f1, $f2           # encoding: [0x18,0x08,0x02,0x46]
+# adda.s  $f1, $f2
+#
+# # DISABLED: suba.s  $f3, $f4           # encoding: [0x19,0x18,0x04,0x46]
+# suba.s  $f3, $f4
+#
+# # DISABLED: mula.s  $f5, $f6           # encoding: [0x1a,0x28,0x06,0x46]
+# mula.s  $f5, $f6
+#
+# # DISABLED: madda.s $f7, $f8           # encoding: [0x1e,0x38,0x08,0x46]
+# madda.s $f7, $f8
+#
+# # DISABLED: msuba.s $f9, $f10          # encoding: [0x1f,0x48,0x0a,0x46]
+# msuba.s $f9, $f10
+#
+# # DISABLED: madd.s  $f0, $f1, $f2      # encoding: [0x1c,0x08,0x02,0x46]
+# madd.s  $f0, $f1, $f2
+#
+# # DISABLED: msub.s  $f3, $f4, $f5      # encoding: [0xdd,0x20,0x05,0x46]
+# msub.s  $f3, $f4, $f5
+
+# TODO: R5900-Specific FPU Instructions (max.s, min.s, rsqrt.s)
+# #------------------------------------------------------------------------------
+# # R5900-Specific: FP Min/Max Instructions
+# #------------------------------------------------------------------------------
+#
+# # DISABLED: max.s   $f0, $f1, $f2      # encoding: [0x28,0x08,0x02,0x46]
+# max.s   $f0, $f1, $f2
+#
+# # DISABLED: min.s   $f3, $f4, $f5      # encoding: [0xe9,0x20,0x05,0x46]
+# min.s   $f3, $f4, $f5
+#
+# #------------------------------------------------------------------------------
+# # R5900-Specific: RSQRT.S (3-operand: fd = fs / sqrt(ft))
+# # Note: differs from MIPS IV 2-operand rsqrt.s
+# #------------------------------------------------------------------------------
+#
+# # DISABLED: rsqrt.s $f6, $f7, $f8      # encoding: [0x96,0x39,0x08,0x46]
+# rsqrt.s $f6, $f7, $f8

>From ee4222711dab751a434e2872e9aba0ee15164f40 Mon Sep 17 00:00:00 2001
From: Rick Gaiser <rgaiser at gmail.com>
Date: Fri, 30 Jan 2026 17:21:17 +0100
Subject: [PATCH 3/4] [MIPS] R5900: Invalidate unsupported FPU instructions

Add predicates to exclude FPU instructions not supported by the R5900:
- Add C_COND_NOTR5900_M multiclass for unsupported compare conditions
  (C.UN, C.UEQ, C.ULT, C.ULE, C.SF, C.NGLE, C.SEQ, C.NGL, C.LT, C.NGE,
  C.LE, C.NGT)
- Add C_COND_NOTR5900_ALIASES multiclass for corresponding aliases
- Add ISA_MIPS2_NOT_R5900 predicate for ROUND/TRUNC/CEIL/FLOOR.W.S
- Update MicroMips to use C_COND_NOTR5900_ALIASES
- Add comprehensive tests for invalid R5900 FPU instructions

The R5900 FPU only supports 4 compare conditions (C.F, C.EQ, C.LT, C.LE)
and lacks the MIPS II rounding instructions.
---
 llvm/lib/Target/Mips/MicroMipsInstrFPU.td |   6 ++
 llvm/lib/Target/Mips/MipsInstrFPU.td      |  92 +++++++++++------
 llvm/test/MC/Mips/r5900-invalid.s         | 118 ++++++++++++++++++++++
 3 files changed, 184 insertions(+), 32 deletions(-)

diff --git a/llvm/lib/Target/Mips/MicroMipsInstrFPU.td b/llvm/lib/Target/Mips/MicroMipsInstrFPU.td
index 22890dc35baa3..bdf931a52dadb 100644
--- a/llvm/lib/Target/Mips/MicroMipsInstrFPU.td
+++ b/llvm/lib/Target/Mips/MicroMipsInstrFPU.td
@@ -405,10 +405,16 @@ let DecoderNamespace = "Mips64" in
 
 defm S_MM   : C_COND_ALIASES<"s", FGR32Opnd>, HARDFLOAT,
               ISA_MICROMIPS32_NOT_MIPS32R6;
+defm S_MM   : C_COND_NOTR5900_ALIASES<"s", FGR32Opnd>, HARDFLOAT,
+              ISA_MICROMIPS32_NOT_MIPS32R6;
 defm D32_MM : C_COND_ALIASES<"d", AFGR64Opnd>, HARDFLOAT,
               ISA_MICROMIPS32_NOT_MIPS32R6, FGR_32;
+defm D32_MM : C_COND_NOTR5900_ALIASES<"d", AFGR64Opnd>, HARDFLOAT,
+              ISA_MICROMIPS32_NOT_MIPS32R6, FGR_32;
 defm D64_MM : C_COND_ALIASES<"d", FGR64Opnd>, HARDFLOAT,
               ISA_MICROMIPS32_NOT_MIPS32R6, FGR_64;
+defm D64_MM : C_COND_NOTR5900_ALIASES<"d", FGR64Opnd>, HARDFLOAT,
+              ISA_MICROMIPS32_NOT_MIPS32R6, FGR_64;
 
 defm : BC1_ALIASES<BC1T_MM, "bc1t", BC1F_MM, "bc1f">,
        ISA_MICROMIPS32_NOT_MIPS32R6, HARDFLOAT;
diff --git a/llvm/lib/Target/Mips/MipsInstrFPU.td b/llvm/lib/Target/Mips/MipsInstrFPU.td
index da0887cdcc232..cad27f3b5dd6a 100644
--- a/llvm/lib/Target/Mips/MipsInstrFPU.td
+++ b/llvm/lib/Target/Mips/MipsInstrFPU.td
@@ -297,33 +297,37 @@ multiclass C_COND_M<string TypeStr, RegisterOperand RC, bits<5> fmt,
     let BaseOpcode = "c.f."#NAME;
     let isCommutable = 1;
   }
-  def C_UN_#NAME : MMRel, C_COND_FT<"un", TypeStr, RC, itin>,
-                   C_COND_FM<fmt, 1> {
-    let BaseOpcode = "c.un."#NAME;
-    let isCommutable = 1;
-  }
   def C_EQ_#NAME : MMRel, C_COND_FT<"eq", TypeStr, RC, itin>,
                    C_COND_FM<fmt, 2> {
     let BaseOpcode = "c.eq."#NAME;
     let isCommutable = 1;
   }
+  def C_OLT_#NAME : MMRel, C_COND_FT<"olt", TypeStr, RC, itin>,
+                    C_COND_FM<fmt, 4> {
+    let BaseOpcode = "c.olt."#NAME;
+  }
+  def C_OLE_#NAME : MMRel, C_COND_FT<"ole", TypeStr, RC, itin>,
+                    C_COND_FM<fmt, 6> {
+    let BaseOpcode = "c.ole."#NAME;
+  }
+}
+
+multiclass C_COND_NOTR5900_M<string TypeStr, RegisterOperand RC, bits<5> fmt,
+                             InstrItinClass itin> {
+  def C_UN_#NAME : MMRel, C_COND_FT<"un", TypeStr, RC, itin>,
+                   C_COND_FM<fmt, 1> {
+    let BaseOpcode = "c.un."#NAME;
+    let isCommutable = 1;
+  }
   def C_UEQ_#NAME : MMRel, C_COND_FT<"ueq", TypeStr, RC, itin>,
                     C_COND_FM<fmt, 3> {
     let BaseOpcode = "c.ueq."#NAME;
     let isCommutable = 1;
   }
-  def C_OLT_#NAME : MMRel, C_COND_FT<"olt", TypeStr, RC, itin>,
-                    C_COND_FM<fmt, 4> {
-    let BaseOpcode = "c.olt."#NAME;
-  }
   def C_ULT_#NAME : MMRel, C_COND_FT<"ult", TypeStr, RC, itin>,
                     C_COND_FM<fmt, 5> {
     let BaseOpcode = "c.ult."#NAME;
   }
-  def C_OLE_#NAME : MMRel, C_COND_FT<"ole", TypeStr, RC, itin>,
-                    C_COND_FM<fmt, 6> {
-    let BaseOpcode = "c.ole."#NAME;
-  }
   def C_ULE_#NAME : MMRel, C_COND_FT<"ule", TypeStr, RC, itin>,
                      C_COND_FM<fmt, 7> {
     let BaseOpcode = "c.ule."#NAME;
@@ -376,29 +380,44 @@ multiclass C_COND_M<string TypeStr, RegisterOperand RC, bits<5> fmt,
 
 let AdditionalPredicates = [NotInMicroMips] in {
 defm S : C_COND_M<"s", FGR32Opnd, 16, II_C_CC_S>, ISA_MIPS1_NOT_32R6_64R6;
+defm S : C_COND_NOTR5900_M<"s", FGR32Opnd, 16, II_C_CC_S>,
+         ISA_MIPS1_NOT_32R6_64R6_R5900;
 defm D32 : C_COND_M<"d", AFGR64Opnd, 17, II_C_CC_D>, ISA_MIPS1_NOT_32R6_64R6,
            FGR_32;
-let DecoderNamespace = "MipsFP64" in
+defm D32 : C_COND_NOTR5900_M<"d", AFGR64Opnd, 17, II_C_CC_D>,
+           ISA_MIPS1_NOT_32R6_64R6, FGR_32;
+let DecoderNamespace = "MipsFP64" in {
 defm D64 : C_COND_M<"d", FGR64Opnd, 17, II_C_CC_D>, ISA_MIPS1_NOT_32R6_64R6,
            FGR_64;
+defm D64 : C_COND_NOTR5900_M<"d", FGR64Opnd, 17, II_C_CC_D>,
+           ISA_MIPS1_NOT_32R6_64R6, FGR_64;
+}
 }
 //===----------------------------------------------------------------------===//
 // Floating Point Instructions
 //===----------------------------------------------------------------------===//
 let AdditionalPredicates = [NotInMicroMips], mayRaiseFPException = 1 in {
-  def ROUND_W_S : MMRel, StdMMR6Rel,
+  def ROUND_W_S : MMRel,
+                  StdMMR6Rel,
                   ABSS_FT<"round.w.s", FGR32Opnd, FGR32Opnd, II_ROUND>,
-                  ABSS_FM<0xc, 16>, ISA_MIPS2;
+                  ABSS_FM<0xc, 16>,
+                  ISA_MIPS2_NOT_R5900;
   defm ROUND_W  : ROUND_M<"round.w.d", II_ROUND>, ABSS_FM<0xc, 17>, ISA_MIPS2;
-  def TRUNC_W_S : MMRel, StdMMR6Rel,
+  def TRUNC_W_S : MMRel,
+                  StdMMR6Rel,
                   ABSS_FT<"trunc.w.s", FGR32Opnd, FGR32Opnd, II_TRUNC>,
-                  ABSS_FM<0xd, 16>, ISA_MIPS2;
-  def CEIL_W_S  : MMRel, StdMMR6Rel,
-                  ABSS_FT<"ceil.w.s", FGR32Opnd, FGR32Opnd, II_CEIL>,
-                  ABSS_FM<0xe, 16>, ISA_MIPS2;
-  def FLOOR_W_S : MMRel, StdMMR6Rel,
+                  ABSS_FM<0xd, 16>,
+                  ISA_MIPS2_NOT_R5900;
+  def CEIL_W_S : MMRel,
+                 StdMMR6Rel,
+                 ABSS_FT<"ceil.w.s", FGR32Opnd, FGR32Opnd, II_CEIL>,
+                 ABSS_FM<0xe, 16>,
+                 ISA_MIPS2_NOT_R5900;
+  def FLOOR_W_S : MMRel,
+                  StdMMR6Rel,
                   ABSS_FT<"floor.w.s", FGR32Opnd, FGR32Opnd, II_FLOOR>,
-                  ABSS_FM<0xf, 16>, ISA_MIPS2;
+                  ABSS_FM<0xf, 16>,
+                  ISA_MIPS2_NOT_R5900;
 
   defm TRUNC_W : ROUND_M<"trunc.w.d", II_TRUNC>, ABSS_FM<0xd, 17>, ISA_MIPS2;
   defm CEIL_W  : ROUND_M<"ceil.w.d", II_CEIL>, ABSS_FM<0xe, 17>, ISA_MIPS2;
@@ -888,24 +907,27 @@ multiclass C_COND_ALIASES<string TypeStr, RegisterOperand RC> {
   def : MipsInstAlias<!strconcat("c.f.", TypeStr, " $fs, $ft"),
                       (!cast<Instruction>("C_F_"#NAME) FCC0,
                                                        RC:$fs, RC:$ft), 1>;
-  def : MipsInstAlias<!strconcat("c.un.", TypeStr, " $fs, $ft"),
-                      (!cast<Instruction>("C_UN_"#NAME) FCC0,
-                                                        RC:$fs, RC:$ft), 1>;
   def : MipsInstAlias<!strconcat("c.eq.", TypeStr, " $fs, $ft"),
                       (!cast<Instruction>("C_EQ_"#NAME) FCC0,
                                                         RC:$fs, RC:$ft), 1>;
-  def : MipsInstAlias<!strconcat("c.ueq.", TypeStr, " $fs, $ft"),
-                      (!cast<Instruction>("C_UEQ_"#NAME) FCC0,
-                                                         RC:$fs, RC:$ft), 1>;
   def : MipsInstAlias<!strconcat("c.olt.", TypeStr, " $fs, $ft"),
                       (!cast<Instruction>("C_OLT_"#NAME) FCC0,
                                                          RC:$fs, RC:$ft), 1>;
-  def : MipsInstAlias<!strconcat("c.ult.", TypeStr, " $fs, $ft"),
-                      (!cast<Instruction>("C_ULT_"#NAME) FCC0,
-                                                         RC:$fs, RC:$ft), 1>;
   def : MipsInstAlias<!strconcat("c.ole.", TypeStr, " $fs, $ft"),
                       (!cast<Instruction>("C_OLE_"#NAME) FCC0,
                                                          RC:$fs, RC:$ft), 1>;
+}
+
+multiclass C_COND_NOTR5900_ALIASES<string TypeStr, RegisterOperand RC> {
+  def : MipsInstAlias<!strconcat("c.un.", TypeStr, " $fs, $ft"),
+                      (!cast<Instruction>("C_UN_"#NAME) FCC0,
+                                                        RC:$fs, RC:$ft), 1>;
+  def : MipsInstAlias<!strconcat("c.ueq.", TypeStr, " $fs, $ft"),
+                      (!cast<Instruction>("C_UEQ_"#NAME) FCC0,
+                                                         RC:$fs, RC:$ft), 1>;
+  def : MipsInstAlias<!strconcat("c.ult.", TypeStr, " $fs, $ft"),
+                      (!cast<Instruction>("C_ULT_"#NAME) FCC0,
+                                                         RC:$fs, RC:$ft), 1>;
   def : MipsInstAlias<!strconcat("c.ule.", TypeStr, " $fs, $ft"),
                       (!cast<Instruction>("C_ULE_"#NAME) FCC0,
                                                          RC:$fs, RC:$ft), 1>;
@@ -947,10 +969,16 @@ multiclass BC1_ALIASES<Instruction BCTrue, string BCTrueString,
 let AdditionalPredicates = [NotInMicroMips] in {
   defm S   : C_COND_ALIASES<"s", FGR32Opnd>, HARDFLOAT,
              ISA_MIPS1_NOT_32R6_64R6;
+  defm S   : C_COND_NOTR5900_ALIASES<"s", FGR32Opnd>, HARDFLOAT,
+             ISA_MIPS1_NOT_32R6_64R6_R5900;
   defm D32 : C_COND_ALIASES<"d", AFGR64Opnd>, HARDFLOAT,
              ISA_MIPS1_NOT_32R6_64R6, FGR_32;
+  defm D32 : C_COND_NOTR5900_ALIASES<"d", AFGR64Opnd>, HARDFLOAT,
+             ISA_MIPS1_NOT_32R6_64R6, FGR_32;
   defm D64 : C_COND_ALIASES<"d", FGR64Opnd>, HARDFLOAT,
              ISA_MIPS1_NOT_32R6_64R6, FGR_64;
+  defm D64 : C_COND_NOTR5900_ALIASES<"d", FGR64Opnd>, HARDFLOAT,
+             ISA_MIPS1_NOT_32R6_64R6, FGR_64;
 
   defm : BC1_ALIASES<BC1T, "bc1t", BC1F, "bc1f">, ISA_MIPS1_NOT_32R6_64R6,
          HARDFLOAT;
diff --git a/llvm/test/MC/Mips/r5900-invalid.s b/llvm/test/MC/Mips/r5900-invalid.s
index 4b1eed2f56bcc..74e8e8ff44241 100644
--- a/llvm/test/MC/Mips/r5900-invalid.s
+++ b/llvm/test/MC/Mips/r5900-invalid.s
@@ -4,6 +4,8 @@
 # - No 64-bit multiply/divide (DMULT/DMULTU/DDIV/DDIVU)
 # - No LL/SC atomic instructions
 # - No COP3 instructions (LWC3, SWC3, LDC3, SDC3)
+# - No ROUND/TRUNC/CEIL/FLOOR.W.S (uses CVT.W.S instead)
+# - No double-precision FPU (single float only)
 #
 # RUN: not llvm-mc %s -triple=mips64el-unknown-linux -mcpu=r5900 2>%t1
 # RUN: FileCheck %s < %t1
@@ -57,3 +59,119 @@
 
 # CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
         sdc3    $4, 0($5)
+
+# =============================================================================
+# Single-precision FP compare instructions that R5900 does NOT support
+# (R5900 only supports c.f.s, c.eq.s, c.olt.s, c.ole.s)
+# =============================================================================
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.un.s  $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.ueq.s $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.ult.s $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.ule.s $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.sf.s  $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.ngle.s $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.seq.s $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.ngl.s $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.lt.s  $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.nge.s $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.le.s  $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.ngt.s $f4, $f6
+
+# =============================================================================
+# FPU rounding instructions that R5900 does NOT support
+# (R5900 uses CVT.W.S which always truncates toward zero)
+# =============================================================================
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        round.w.s $f4, $f5
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        trunc.w.s $f6, $f7
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        ceil.w.s  $f8, $f9
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        floor.w.s $f10, $f11
+
+# =============================================================================
+# Double-precision FPU instructions that R5900 does NOT support
+# (R5900 has single-precision FPU only)
+# =============================================================================
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        add.d   $f4, $f6, $f8
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        sub.d   $f4, $f6, $f8
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        mul.d   $f4, $f6, $f8
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        div.d   $f4, $f6, $f8
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        abs.d   $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        neg.d   $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        sqrt.d  $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        mov.d   $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        cvt.s.d $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        cvt.d.s $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        cvt.d.w $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        cvt.w.d $f4, $f6
+
+# 64-bit FP load/store
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        ldc1    $f4, 0($5)
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        sdc1    $f4, 0($5)
+
+# Double-precision comparisons
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.eq.d  $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.lt.d  $f4, $f6
+
+# CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: instruction requires a CPU feature not currently enabled
+        c.le.d  $f4, $f6

>From 197832d0537e093c3115dcd38632f873ee548134 Mon Sep 17 00:00:00 2001
From: Rick Gaiser <rgaiser at gmail.com>
Date: Sun, 1 Feb 2026 12:51:16 +0100
Subject: [PATCH 4/4] [MIPS] Add R5900 FPU compare CodeGen test

---
 llvm/test/CodeGen/Mips/r5900-fpu-compare.ll | 210 ++++++++++++++++++++
 1 file changed, 210 insertions(+)
 create mode 100644 llvm/test/CodeGen/Mips/r5900-fpu-compare.ll

diff --git a/llvm/test/CodeGen/Mips/r5900-fpu-compare.ll b/llvm/test/CodeGen/Mips/r5900-fpu-compare.ll
new file mode 100644
index 0000000000000..d63a0d25b301d
--- /dev/null
+++ b/llvm/test/CodeGen/Mips/r5900-fpu-compare.ll
@@ -0,0 +1,210 @@
+; RUN: llc -mtriple=mips64el -mcpu=r5900 < %s | FileCheck %s
+;
+; Test that R5900 FPU comparisons only generate supported instructions.
+; R5900 only supports: C.F (0x30), C.EQ (0x32), C.OLT (0x34), C.OLE (0x36)
+;
+; Unsupported conditions are transformed:
+; - Unordered (ULT, ULE, UEQ, etc.) → Ordered equivalents (R5900 has no NaN)
+; - Greater-than → Less-than with swapped operands
+
+;-----------------------------------------------------------------------------
+; Ordered comparisons
+;-----------------------------------------------------------------------------
+
+define i32 @test_oeq(float %a, float %b) {
+; CHECK-LABEL: test_oeq:
+; CHECK: c.eq.s $f12, $f13
+; CHECK-NOT: c.ueq
+  %cmp = fcmp oeq float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+define i32 @test_olt(float %a, float %b) {
+; CHECK-LABEL: test_olt:
+; CHECK: c.olt.s $f12, $f13
+; CHECK-NOT: c.ult
+  %cmp = fcmp olt float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+define i32 @test_ole(float %a, float %b) {
+; CHECK-LABEL: test_ole:
+; CHECK: c.ole.s $f12, $f13
+; CHECK-NOT: c.ule
+  %cmp = fcmp ole float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+define i32 @test_ogt(float %a, float %b) {
+; CHECK-LABEL: test_ogt:
+; CHECK: c.olt.s $f13, $f12
+; Swapped operands: a > b  →  b < a
+  %cmp = fcmp ogt float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+define i32 @test_oge(float %a, float %b) {
+; CHECK-LABEL: test_oge:
+; CHECK: c.ole.s $f13, $f12
+; Swapped operands: a >= b  →  b <= a
+  %cmp = fcmp oge float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+;-----------------------------------------------------------------------------
+; Unordered comparisons - mapped to ordered (R5900 FPU has no NaN)
+;-----------------------------------------------------------------------------
+
+define i32 @test_ueq(float %a, float %b) {
+; CHECK-LABEL: test_ueq:
+; CHECK: c.eq.s $f12, $f13
+; CHECK-NOT: c.ueq
+  %cmp = fcmp ueq float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+define i32 @test_ult(float %a, float %b) {
+; CHECK-LABEL: test_ult:
+; CHECK: c.olt.s $f12, $f13
+; CHECK-NOT: c.ult
+  %cmp = fcmp ult float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+define i32 @test_ule(float %a, float %b) {
+; CHECK-LABEL: test_ule:
+; CHECK: c.ole.s $f12, $f13
+; CHECK-NOT: c.ule
+  %cmp = fcmp ule float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+define i32 @test_ugt(float %a, float %b) {
+; CHECK-LABEL: test_ugt:
+; CHECK: c.olt.s $f13, $f12
+; Swapped operands: a > b  →  b < a
+  %cmp = fcmp ugt float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+define i32 @test_uge(float %a, float %b) {
+; CHECK-LABEL: test_uge:
+; CHECK: c.ole.s $f13, $f12
+; Swapped operands: a >= b  →  b <= a
+  %cmp = fcmp uge float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+;-----------------------------------------------------------------------------
+; Simple comparisons (non-prefixed)
+;-----------------------------------------------------------------------------
+
+define i32 @test_eq(float %a, float %b) {
+; CHECK-LABEL: test_eq:
+; CHECK: c.eq.s $f12, $f13
+  %cmp = fcmp oeq float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+define i32 @test_lt(float %a, float %b) {
+; CHECK-LABEL: test_lt:
+; CHECK: c.olt.s $f12, $f13
+  %cmp = fcmp olt float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+define i32 @test_le(float %a, float %b) {
+; CHECK-LABEL: test_le:
+; CHECK: c.ole.s $f12, $f13
+  %cmp = fcmp ole float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+define i32 @test_gt(float %a, float %b) {
+; CHECK-LABEL: test_gt:
+; CHECK: c.olt.s $f13, $f12
+  %cmp = fcmp ogt float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+define i32 @test_ge(float %a, float %b) {
+; CHECK-LABEL: test_ge:
+; CHECK: c.ole.s $f13, $f12
+  %cmp = fcmp oge float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+;-----------------------------------------------------------------------------
+; Not-equal comparisons
+;-----------------------------------------------------------------------------
+
+define i32 @test_one(float %a, float %b) {
+; CHECK-LABEL: test_one:
+; CHECK: c.eq.s
+; Uses EQ and inverts result via bc1f/bc1t
+  %cmp = fcmp one float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+define i32 @test_une(float %a, float %b) {
+; CHECK-LABEL: test_une:
+; CHECK: c.eq.s
+; Uses EQ and inverts result via bc1f/bc1t
+  %cmp = fcmp une float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+;-----------------------------------------------------------------------------
+; Ordered/Unordered predicates
+;-----------------------------------------------------------------------------
+
+define i32 @test_ord(float %a, float %b) {
+; CHECK-LABEL: test_ord:
+; R5900 has no NaN, so ordered is always true
+; Should optimize or use FCOND_T (inverted F)
+  %cmp = fcmp ord float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+define i32 @test_uno(float %a, float %b) {
+; CHECK-LABEL: test_uno:
+; R5900 has no NaN, so unordered is always false
+; CHECK: c.f.s
+  %cmp = fcmp uno float %a, %b
+  %result = zext i1 %cmp to i32
+  ret i32 %result
+}
+
+;-----------------------------------------------------------------------------
+; Verify no unsupported instructions are generated anywhere
+;-----------------------------------------------------------------------------
+; CHECK-NOT: c.un.s
+; CHECK-NOT: c.ueq.s
+; CHECK-NOT: c.ult.s
+; CHECK-NOT: c.ule.s
+; CHECK-NOT: c.sf.s
+; CHECK-NOT: c.ngle.s
+; CHECK-NOT: c.seq.s
+; CHECK-NOT: c.ngl.s
+; CHECK-NOT: c.lt.s
+; CHECK-NOT: c.nge.s
+; CHECK-NOT: c.le.s
+; CHECK-NOT: c.ngt.s



More information about the llvm-commits mailing list