[llvm] [RISCV] Lower scalar FP (freeze poison) as a fmv/fcvt from x0. (PR #225549)

Craig Topper via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 22 15:56:31 PDT 2026


https://github.com/topperc created https://github.com/llvm/llvm-project/pull/225549

We need to ensure the result frozen result is nan-boxed. If it is not nan-boxed, it will be treated as a nan in FP contexts regardless of what the lower bits are. If the frozen value is also casted to an integer type, the lack of nan-boxing will not be noticed and the integer will be taken from the lower bits of the FP register. This may cause the freeze to appear to be 2 different values.

This patch lowers the (freeze poison) the same as a constantfp 0.0. This ensures the frozen value will be produced by an instruction that will do the nan-boxing.

Fixes #225455.

>From c153c999af534f7655c77681f4639cb75cc8a065 Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Tue, 22 Sep 2026 15:24:14 -0700
Subject: [PATCH] [RISCV] Lower scalar FP (freeze poison) as a fmv/fcvt from
 x0.

We need to ensure the result frozen result is nan-boxed. If it is
not nan-boxed, it may be a nan in FP contexts regardless of what
the lower bits are. If the frozen value is also casted to an integer
type, the lack of nan-boxing will not be noticed and the integer
will be just the lower bits of the FP value. This will cause the
frozen to appear to be 2 different values.

This patch lowers the (freeze poison) the same as a constantfp 0.0.
This ensures the frozen value will be produced by an instruction
that will do the nan-boxing.

Fixes #225455.
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 146 +++++++++++---------
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h   |   2 +
 llvm/test/CodeGen/RISCV/bfloat-imm.ll       |   9 ++
 llvm/test/CodeGen/RISCV/double-imm.ll       |  22 +++
 llvm/test/CodeGen/RISCV/float-imm.ll        |  13 ++
 llvm/test/CodeGen/RISCV/half-imm.ll         |  22 +++
 6 files changed, 147 insertions(+), 67 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 146aef612f3876..cf32ab8befd5ee 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -1171,6 +1171,74 @@ static bool isApplicableToPLIOrPLUI(int Val) {
          Bit15To8 == Bit7To0;
 }
 
+void RISCVDAGToDAGISel::selectFPConstant(SDNode *Node, const SDLoc &DL, MVT VT,
+                                         const APFloat &APF) {
+  bool Is64Bit = Subtarget->is64Bit();
+  bool HasZdinx = Subtarget->hasStdExtZdinx();
+  MVT XLenVT = Subtarget->getXLenVT();
+
+  bool NegZeroF64 = APF.isNegZero() && VT == MVT::f64;
+  SDValue Imm;
+  // For +0.0 or f64 -0.0 we need to start from X0. For all others, we will
+  // create an integer immediate.
+  if (APF.isPosZero() || NegZeroF64) {
+    if (VT == MVT::f64 && HasZdinx && !Is64Bit)
+      Imm = CurDAG->getRegister(RISCV::X0_Pair, MVT::f64);
+    else
+      Imm = CurDAG->getRegister(RISCV::X0, XLenVT);
+  } else {
+    Imm = selectImm(CurDAG, DL, XLenVT, APF.bitcastToAPInt().getSExtValue(),
+                    *Subtarget);
+  }
+
+  unsigned Opc;
+  switch (VT.SimpleTy) {
+  default:
+    llvm_unreachable("Unexpected size");
+  case MVT::bf16:
+    assert(Subtarget->hasStdExtZfbfmin());
+    Opc = RISCV::FMV_H_X;
+    break;
+  case MVT::f16:
+    Opc = Subtarget->hasStdExtZhinxmin() ? RISCV::COPY : RISCV::FMV_H_X;
+    break;
+  case MVT::f32:
+    Opc = Subtarget->hasStdExtZfinx() ? RISCV::COPY : RISCV::FMV_W_X;
+    break;
+  case MVT::f64:
+    // For RV32, we can't move from a GPR, we need to convert instead. This
+    // should only happen for +0.0 and -0.0.
+    assert((Subtarget->is64Bit() || APF.isZero()) && "Unexpected constant");
+    if (HasZdinx)
+      Opc = RISCV::COPY;
+    else
+      Opc = Is64Bit ? RISCV::FMV_D_X : RISCV::FCVT_D_W;
+    break;
+  }
+
+  SDNode *Res;
+  if (VT.SimpleTy == MVT::f16 && Opc == RISCV::COPY)
+    Res = CurDAG->getTargetExtractSubreg(RISCV::sub_16, DL, VT, Imm).getNode();
+  else if (VT.SimpleTy == MVT::f32 && Opc == RISCV::COPY)
+    Res = CurDAG->getTargetExtractSubreg(RISCV::sub_32, DL, VT, Imm).getNode();
+  else if (Opc == RISCV::FCVT_D_W_IN32X || Opc == RISCV::FCVT_D_W)
+    Res = CurDAG->getMachineNode(
+        Opc, DL, VT, Imm,
+        CurDAG->getTargetConstant(RISCVFPRndMode::RNE, DL, XLenVT));
+  else
+    Res = CurDAG->getMachineNode(Opc, DL, VT, Imm);
+
+  // For f64 -0.0, we need to insert a fneg.d idiom.
+  if (NegZeroF64) {
+    Opc = RISCV::FSGNJN_D;
+    if (HasZdinx)
+      Opc = Is64Bit ? RISCV::FSGNJN_D_INX : RISCV::FSGNJN_D_IN32X;
+    Res = CurDAG->getMachineNode(Opc, DL, VT, SDValue(Res, 0), SDValue(Res, 0));
+  }
+
+  ReplaceNode(Node, Res);
+}
+
 void RISCVDAGToDAGISel::Select(SDNode *Node) {
   // If we have a custom node, we have already selected.
   if (Node->isMachineOpcode()) {
@@ -1243,75 +1311,19 @@ void RISCVDAGToDAGISel::Select(SDNode *Node) {
   }
   case ISD::ConstantFP: {
     const APFloat &APF = cast<ConstantFPSDNode>(Node)->getValueAPF();
-
-    bool Is64Bit = Subtarget->is64Bit();
-    bool HasZdinx = Subtarget->hasStdExtZdinx();
-
-    bool NegZeroF64 = APF.isNegZero() && VT == MVT::f64;
-    SDValue Imm;
-    // For +0.0 or f64 -0.0 we need to start from X0. For all others, we will
-    // create an integer immediate.
-    if (APF.isPosZero() || NegZeroF64) {
-      if (VT == MVT::f64 && HasZdinx && !Is64Bit)
-        Imm = CurDAG->getRegister(RISCV::X0_Pair, MVT::f64);
-      else
-        Imm = CurDAG->getRegister(RISCV::X0, XLenVT);
-    } else {
-      Imm = selectImm(CurDAG, DL, XLenVT, APF.bitcastToAPInt().getSExtValue(),
-                      *Subtarget);
-    }
-
-    unsigned Opc;
-    switch (VT.SimpleTy) {
-    default:
-      llvm_unreachable("Unexpected size");
-    case MVT::bf16:
-      assert(Subtarget->hasStdExtZfbfmin());
-      Opc = RISCV::FMV_H_X;
-      break;
-    case MVT::f16:
-      Opc = Subtarget->hasStdExtZhinxmin() ? RISCV::COPY : RISCV::FMV_H_X;
-      break;
-    case MVT::f32:
-      Opc = Subtarget->hasStdExtZfinx() ? RISCV::COPY : RISCV::FMV_W_X;
-      break;
-    case MVT::f64:
-      // For RV32, we can't move from a GPR, we need to convert instead. This
-      // should only happen for +0.0 and -0.0.
-      assert((Subtarget->is64Bit() || APF.isZero()) && "Unexpected constant");
-      if (HasZdinx)
-        Opc = RISCV::COPY;
-      else
-        Opc = Is64Bit ? RISCV::FMV_D_X : RISCV::FCVT_D_W;
-      break;
-    }
-
-    SDNode *Res;
-    if (VT.SimpleTy == MVT::f16 && Opc == RISCV::COPY) {
-      Res =
-          CurDAG->getTargetExtractSubreg(RISCV::sub_16, DL, VT, Imm).getNode();
-    } else if (VT.SimpleTy == MVT::f32 && Opc == RISCV::COPY) {
-      Res =
-          CurDAG->getTargetExtractSubreg(RISCV::sub_32, DL, VT, Imm).getNode();
-    } else if (Opc == RISCV::FCVT_D_W_IN32X || Opc == RISCV::FCVT_D_W)
-      Res = CurDAG->getMachineNode(
-          Opc, DL, VT, Imm,
-          CurDAG->getTargetConstant(RISCVFPRndMode::RNE, DL, XLenVT));
-    else
-      Res = CurDAG->getMachineNode(Opc, DL, VT, Imm);
-
-    // For f64 -0.0, we need to insert a fneg.d idiom.
-    if (NegZeroF64) {
-      Opc = RISCV::FSGNJN_D;
-      if (HasZdinx)
-        Opc = Is64Bit ? RISCV::FSGNJN_D_INX : RISCV::FSGNJN_D_IN32X;
-      Res =
-          CurDAG->getMachineNode(Opc, DL, VT, SDValue(Res, 0), SDValue(Res, 0));
-    }
-
-    ReplaceNode(Node, Res);
+    selectFPConstant(Node, DL, VT, APF);
     return;
   }
+  case ISD::FREEZE:
+    // Lower (freeze poison) to a zero producing instruction so we properly
+    // nan-box to FLEN.
+    if (VT.isFloatingPoint() && !VT.isVector() && Subtarget->hasStdExtF() &&
+        Node->getOperand(0).getOpcode() == ISD::POISON) {
+      APFloat Zero = APFloat::getZero(VT.getFltSemantics());
+      selectFPConstant(Node, DL, VT, Zero);
+      return;
+    }
+    break;
   case RISCVISD::BuildGPRPair:
   case RISCVISD::BuildPairF64:
   case RISCVISD::BuildPairGPRVec: {
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
index da08a6032d94ff..5012f746d95530 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
@@ -108,6 +108,8 @@ class RISCVDAGToDAGISel : public SelectionDAGISel {
                                        SDValue X, unsigned Msb, unsigned Lsb);
   bool tryIndexedLoad(SDNode *Node);
   bool tryWideningMulAcc(SDNode *Node, const SDLoc &DL);
+  void selectFPConstant(SDNode *Node, const SDLoc &DL, MVT VT,
+                        const APFloat &APF);
 
   bool selectShiftMask(SDValue N, unsigned ShiftWidth, SDValue &ShAmt);
   bool selectShiftMaskXLen(SDValue N, SDValue &ShAmt) {
diff --git a/llvm/test/CodeGen/RISCV/bfloat-imm.ll b/llvm/test/CodeGen/RISCV/bfloat-imm.ll
index 3c94b7bd70ea3f..d1590ba8e40cb0 100644
--- a/llvm/test/CodeGen/RISCV/bfloat-imm.ll
+++ b/llvm/test/CodeGen/RISCV/bfloat-imm.ll
@@ -43,3 +43,12 @@ define bfloat @bfloat_negative_zero() nounwind {
 ; CHECK-NEXT:    ret
   ret bfloat -0.0
 }
+
+define bfloat @freeze_poison() nounwind {
+; CHECK-LABEL: freeze_poison:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    fmv.h.x fa0, zero
+; CHECK-NEXT:    ret
+  %a = freeze bfloat poison
+  ret bfloat %a
+}
diff --git a/llvm/test/CodeGen/RISCV/double-imm.ll b/llvm/test/CodeGen/RISCV/double-imm.ll
index 9f31c95f64dc36..b8719fb5b9109a 100644
--- a/llvm/test/CodeGen/RISCV/double-imm.ll
+++ b/llvm/test/CodeGen/RISCV/double-imm.ll
@@ -170,3 +170,25 @@ entry:
   %d. = select i1 %tobool.not, double %d, double -0.000000e+00
   ret double %d.
 }
+
+define double @freeze_poison() nounwind {
+; CHECK32D-LABEL: freeze_poison:
+; CHECK32D:       # %bb.0:
+; CHECK32D-NEXT:    fcvt.d.w fa0, zero
+; CHECK32D-NEXT:    ret
+;
+; CHECK64D-LABEL: freeze_poison:
+; CHECK64D:       # %bb.0:
+; CHECK64D-NEXT:    fmv.d.x fa0, zero
+; CHECK64D-NEXT:    ret
+;
+; CHECKRV32ZDINX-LABEL: freeze_poison:
+; CHECKRV32ZDINX:       # %bb.0:
+; CHECKRV32ZDINX-NEXT:    ret
+;
+; CHECKRV64ZDINX-LABEL: freeze_poison:
+; CHECKRV64ZDINX:       # %bb.0:
+; CHECKRV64ZDINX-NEXT:    ret
+  %a = freeze double poison
+  ret double %a
+}
diff --git a/llvm/test/CodeGen/RISCV/float-imm.ll b/llvm/test/CodeGen/RISCV/float-imm.ll
index 610c72b5f932ef..0742d2d50bc373 100644
--- a/llvm/test/CodeGen/RISCV/float-imm.ll
+++ b/llvm/test/CodeGen/RISCV/float-imm.ll
@@ -68,3 +68,16 @@ define float @float_negative_zero(ptr %pf) nounwind {
 ; CHECKZFINX-NEXT:    ret
   ret float -0.0
 }
+
+define float @freeze_poison() nounwind {
+; CHECK-LABEL: freeze_poison:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    fmv.w.x fa0, zero
+; CHECK-NEXT:    ret
+;
+; CHECKZFINX-LABEL: freeze_poison:
+; CHECKZFINX:       # %bb.0:
+; CHECKZFINX-NEXT:    ret
+  %a = freeze float poison
+  ret float %a
+}
diff --git a/llvm/test/CodeGen/RISCV/half-imm.ll b/llvm/test/CodeGen/RISCV/half-imm.ll
index 8fbaca5c8adc0d..0ed1a095796bf4 100644
--- a/llvm/test/CodeGen/RISCV/half-imm.ll
+++ b/llvm/test/CodeGen/RISCV/half-imm.ll
@@ -134,3 +134,25 @@ define half @half_negative_zero(ptr %pf) nounwind {
 ; CHECKIZHINXMIN-NEXT:    ret
   ret half -0.0
 }
+
+define half @freeze_poison() nounwind {
+; CHECK-LABEL: freeze_poison:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    fmv.h.x fa0, zero
+; CHECK-NEXT:    ret
+;
+; CHECKIZHINX-LABEL: freeze_poison:
+; CHECKIZHINX:       # %bb.0:
+; CHECKIZHINX-NEXT:    ret
+;
+; CHECKIZFHMIN-LABEL: freeze_poison:
+; CHECKIZFHMIN:       # %bb.0:
+; CHECKIZFHMIN-NEXT:    fmv.h.x fa0, zero
+; CHECKIZFHMIN-NEXT:    ret
+;
+; CHECKIZHINXMIN-LABEL: freeze_poison:
+; CHECKIZHINXMIN:       # %bb.0:
+; CHECKIZHINXMIN-NEXT:    ret
+  %a = freeze half poison
+  ret half %a
+}



More information about the llvm-commits mailing list