[llvm-branch-commits] [llvm] [APFloat][SelectionDAG] Support Float8E5M3FNU in `convert.{to, from}.arbitrary.fp` (PR #216387)

Chinmay Deshpande via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Sat Aug 15 11:41:34 PDT 2026


https://github.com/chinmaydd updated https://github.com/llvm/llvm-project/pull/216387

>From 6b1466f19f70e7d40a9111458193fa8232df7eba Mon Sep 17 00:00:00 2001
From: Chinmay Deshpande <chdeshpa at amd.com>
Date: Fri, 14 Aug 2026 14:32:38 -0400
Subject: [PATCH] [APFloat][SelectionDAG] Support Float8E5M3FNU in
 convert.{to,from}.arbitrary.fp

Float8E5M3FNU was already accepted by the IR verifier, because
isValidArbitraryFPFormat is defined in terms of
getArbitraryFPFormatSizeInBits and that table covers it. It was missing
from getArbitraryFPSemantics, so SelectionDAGBuilder rejected
it with "not implemented format" and the verifier-clean IR failed to
compile. Add the mapping and the corresponding entries in the
expandCONVERT_{TO,FROM}_ARBITRARY_FP format allowlists.

Unlike every other format the expansions handle so far, Float8E5M3FNU is
unsigned: it has no sign bit, so all 8 bits go to a 5-bit exponent and a
3-bit significand.

Since an unsigned format cannot represent a negative value, a negative
input now saturates to zero when the saturate flag is set, and is poison
otherwise. -0.0 is excluded from that and still converts to +0, and the
check is ordered before the NaN case so a negative NaN still produces the
NaN encoding. APFloat treats constructing a negative value in an unsigned
semantics as unreachable, so there was no existing behaviour to match.

Change-Id: Ibaeacdb9e20b2ac6d08da80b824ec439f2ba8615
---
 .../CodeGen/SelectionDAG/TargetLowering.cpp   |  48 +++-
 llvm/lib/Support/APFloat.cpp                  |   4 +-
 .../AMDGPU/float-to-arbitrary-fp-e5m3fnu.ll   | 263 ++++++++++++++++++
 .../test/CodeGen/X86/float-to-arbitrary-fp.ll | 189 +++++++++++++
 llvm/unittests/ADT/APFloatTest.cpp            |   3 +-
 5 files changed, 493 insertions(+), 14 deletions(-)
 create mode 100644 llvm/test/CodeGen/AMDGPU/float-to-arbitrary-fp-e5m3fnu.ll

diff --git a/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp b/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp
index af93a2a3d2388..fed2f74751449 100644
--- a/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp
@@ -9341,6 +9341,7 @@ SDValue TargetLowering::expandCONVERT_TO_ARBITRARY_FP(SDNode *Node,
   switch (Sem) {
   case APFloatBase::S_Float8E5M2:
   case APFloatBase::S_Float8E4M3FN:
+  case APFloatBase::S_Float8E5M3FNU:
   case APFloatBase::S_Float6E3M2FN:
   case APFloatBase::S_Float6E2M3FN:
   case APFloatBase::S_Float4E2M1FN:
@@ -9372,7 +9373,9 @@ SDValue TargetLowering::expandCONVERT_TO_ARBITRARY_FP(SDNode *Node,
   const unsigned DstBits = APFloat::getSizeInBits(DstSem);
   const unsigned DstPrecision = APFloat::semanticsPrecision(DstSem);
   const unsigned DstMant = DstPrecision - 1;
-  const unsigned DstExpBits = DstBits - DstMant - 1;
+  // Unsigned formats such as Float8E5M3FNU spend no bit on the sign.
+  const bool DstHasSign = APFloat::semanticsHasSignedRepr(DstSem);
+  const unsigned DstExpBits = DstBits - (DstHasSign ? 1 : 0) - DstMant;
   const int DstBias = 1 - APFloat::semanticsMinExponent(DstSem);
   const unsigned DstExpMax = (1U << DstExpBits) - 1;
   const uint64_t DstMantMask = (DstMant > 0) ? ((1ULL << DstMant) - 1) : 0;
@@ -9545,10 +9548,14 @@ SDValue TargetLowering::expandCONVERT_TO_ARBITRARY_FP(SDNode *Node,
       DAG.getNode(ISD::ADD, dl, IntVT, NewExp,
                   DAG.getNode(ISD::ZERO_EXTEND, dl, IntVT, MantOverflow));
 
-  // Precompute sign shifted to MSB of destination.
+  // Precompute sign shifted to MSB of destination. Unsigned formats have no
+  // sign bit, so nothing is merged in and negative inputs are resolved
+  // separately when assembling the final result.
   SDValue SignShifted =
-      DAG.getNode(ISD::SHL, dl, IntVT, SignBit,
-                  DAG.getShiftAmountConstant(DstBits - 1, IntVT, dl));
+      DstHasSign
+          ? DAG.getNode(ISD::SHL, dl, IntVT, SignBit,
+                        DAG.getShiftAmountConstant(DstBits - 1, IntVT, dl))
+          : Zero;
 
   // Destination denormal conversion (when new_exp <= 0).
   // Shift the mantissa right by 1 - new_exp additional bits and set the
@@ -9731,6 +9738,19 @@ SDValue TargetLowering::expandCONVERT_TO_ARBITRARY_FP(SDNode *Node,
   SDValue Result = FiniteResult;
   Result = DAG.getSelect(dl, IntVT, IsZero, ZeroResult, Result);
   Result = DAG.getSelect(dl, IntVT, IsInf, InfResult, Result);
+
+  // An unsigned destination format cannot encode a negative value. -0.0 is
+  // already covered by the IsZero case above and converts to +0; any other
+  // negative input saturates to zero (the smallest representable value), or is
+  // poison when not saturating. Applied before the NaN case so that a negative
+  // NaN still produces the NaN encoding.
+  if (!DstHasSign) {
+    SDValue IsNegative =
+        DAG.getSetCC(dl, FPSetCCVT, FloatVal, FPZero, ISD::SETOLT);
+    SDValue NegResult = Saturate ? Zero : DAG.getPOISON(IntVT);
+    Result = DAG.getSelect(dl, IntVT, IsNegative, NegResult, Result);
+  }
+
   Result = DAG.getSelect(dl, IntVT, IsNaN, NaNResult, Result);
 
   // Truncate to destination integer type.
@@ -9752,6 +9772,7 @@ TargetLowering::expandCONVERT_FROM_ARBITRARY_FP(SDNode *Node,
   switch (Sem) {
   case APFloatBase::S_Float8E5M2:
   case APFloatBase::S_Float8E4M3FN:
+  case APFloatBase::S_Float8E5M3FNU:
   case APFloatBase::S_Float6E3M2FN:
   case APFloatBase::S_Float6E2M3FN:
   case APFloatBase::S_Float4E2M1FN:
@@ -9767,7 +9788,9 @@ TargetLowering::expandCONVERT_FROM_ARBITRARY_FP(SDNode *Node,
   const unsigned SrcBits = APFloat::getSizeInBits(SrcSem);
   const unsigned SrcPrecision = APFloat::semanticsPrecision(SrcSem);
   const unsigned SrcMant = SrcPrecision - 1;
-  const unsigned SrcExp = SrcBits - SrcMant - 1;
+  // Unsigned formats such as Float8E5M3FNU spend no bit on the sign.
+  const bool SrcHasSign = APFloat::semanticsHasSignedRepr(SrcSem);
+  const unsigned SrcExp = SrcBits - (SrcHasSign ? 1 : 0) - SrcMant;
   const int SrcBias = 1 - APFloat::semanticsMinExponent(SrcSem);
   const fltNonfiniteBehavior NFBehavior = SrcSem.nonFiniteBehavior;
 
@@ -9808,13 +9831,16 @@ TargetLowering::expandCONVERT_FROM_ARBITRARY_FP(SDNode *Node,
                               DAG.getShiftAmountConstant(SrcMant, IntVT, dl)),
                   DAG.getConstant(ExpMask, dl, IntVT));
 
-  SDValue SignBit =
-      DAG.getNode(ISD::SRL, dl, IntVT, Src,
-                  DAG.getShiftAmountConstant(SrcBits - 1, IntVT, dl));
-
+  // An unsigned source has no sign bit; bit SrcBits - 1 belongs to the
+  // exponent field, so the result is always non-negative.
   SDValue SignShifted =
-      DAG.getNode(ISD::SHL, dl, IntVT, SignBit,
-                  DAG.getShiftAmountConstant(DstBits - 1, IntVT, dl));
+      SrcHasSign
+          ? DAG.getNode(
+                ISD::SHL, dl, IntVT,
+                DAG.getNode(ISD::SRL, dl, IntVT, Src,
+                            DAG.getShiftAmountConstant(SrcBits - 1, IntVT, dl)),
+                DAG.getShiftAmountConstant(DstBits - 1, IntVT, dl))
+          : Zero;
 
   // Classify the input.
   SDValue ExpAllOnes = DAG.getConstant(ExpMask, dl, IntVT);
diff --git a/llvm/lib/Support/APFloat.cpp b/llvm/lib/Support/APFloat.cpp
index 63007d88ed4d4..21b398e77a64b 100644
--- a/llvm/lib/Support/APFloat.cpp
+++ b/llvm/lib/Support/APFloat.cpp
@@ -6054,11 +6054,11 @@ bool APFloatBase::isValidArbitraryFPFormat(StringRef Format) {
 
 const fltSemantics *APFloatBase::getArbitraryFPSemantics(StringRef Format) {
   // TODO: extend to remaining arbitrary FP types: Float8E4M3, Float8E3M4,
-  // Float8E5M2FNUZ, Float8E4M3FNUZ, Float8E4M3B11FNUZ, Float8E8M0FNU,
-  // Float8E5M3FNU.
+  // Float8E5M2FNUZ, Float8E4M3FNUZ, Float8E4M3B11FNUZ, Float8E8M0FNU.
   return StringSwitch<const fltSemantics *>(Format)
       .Case("Float8E5M2", &semFloat8E5M2)
       .Case("Float8E4M3FN", &semFloat8E4M3FN)
+      .Case("Float8E5M3FNU", &semFloat8E5M3FNU)
       .Case("Float4E2M1FN", &semFloat4E2M1FN)
       .Case("Float6E3M2FN", &semFloat6E3M2FN)
       .Case("Float6E2M3FN", &semFloat6E2M3FN)
diff --git a/llvm/test/CodeGen/AMDGPU/float-to-arbitrary-fp-e5m3fnu.ll b/llvm/test/CodeGen/AMDGPU/float-to-arbitrary-fp-e5m3fnu.ll
new file mode 100644
index 0000000000000..2584e5b9fd119
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/float-to-arbitrary-fp-e5m3fnu.ll
@@ -0,0 +1,263 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=amdgpu10.30-unknown-amdhsa < %s | FileCheck %s
+
+; Generic expansion of llvm.convert.{to,from}.arbitrary.fp for Float8E5M3FNU.
+; gfx1030 has no fp8 conversion hardware, so this exercises
+; TargetLowering::expand{CONVERT_TO,CONVERT_FROM}_ARBITRARY_FP.
+
+define i8 @to_e5m3fnu_1_0() {
+; CHECK-LABEL: to_e5m3fnu_1_0:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x78
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 1.0, metadata !"Float8E5M3FNU", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+define i8 @to_e5m3fnu_1_5() {
+; CHECK-LABEL: to_e5m3fnu_1_5:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x7c
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 1.5, metadata !"Float8E5M3FNU", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+define i8 @to_e5m3fnu_2_0() {
+; CHECK-LABEL: to_e5m3fnu_2_0:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x80
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 2.0, metadata !"Float8E5M3FNU", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; Largest finite value: exp=31, mant=6.
+define i8 @to_e5m3fnu_max_finite() {
+; CHECK-LABEL: to_e5m3fnu_max_finite:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0xfe
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 114688.0, metadata !"Float8E5M3FNU", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; 2^-15 is denormal in this format: exp=0, mant=4.
+define i8 @to_e5m3fnu_denormal() {
+; CHECK-LABEL: to_e5m3fnu_denormal:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 4
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 0x3F00000000000000, metadata !"Float8E5M3FNU", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+define i8 @to_e5m3fnu_pos_zero() {
+; CHECK-LABEL: to_e5m3fnu_pos_zero:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 0.0, metadata !"Float8E5M3FNU", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; -0.0 is representable as +0 even without saturation.
+define i8 @to_e5m3fnu_neg_zero() {
+; CHECK-LABEL: to_e5m3fnu_neg_zero:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float -0.0, metadata !"Float8E5M3FNU", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+define i8 @to_e5m3fnu_nan() {
+; CHECK-LABEL: to_e5m3fnu_nan:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0xff
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 0x7FF8000000000000, metadata !"Float8E5M3FNU", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; The format has no Inf, so a saturating conversion clamps to max finite.
+define i8 @to_e5m3fnu_inf_saturate() {
+; CHECK-LABEL: to_e5m3fnu_inf_saturate:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0xfe
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 0x7FF0000000000000, metadata !"Float8E5M3FNU", metadata !"round.tonearest", i1 true)
+  ret i8 %r
+}
+
+; Negative values are unrepresentable; saturating clamps them to zero.
+define i8 @to_e5m3fnu_negative_saturate() {
+; CHECK-LABEL: to_e5m3fnu_negative_saturate:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float -1.0, metadata !"Float8E5M3FNU", metadata !"round.tonearest", i1 true)
+  ret i8 %r
+}
+
+define i8 @to_e5m3fnu_dynamic(float %x) {
+; CHECK-LABEL: to_e5m3fnu_dynamic:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_frexp_exp_i32_f32_e32 v1, v0
+; CHECK-NEXT:    v_frexp_mant_f32_e32 v3, v0
+; CHECK-NEXT:    s_mov_b32 s4, 0x7fffff
+; CHECK-NEXT:    v_sub_nc_u32_e32 v2, 7, v1
+; CHECK-NEXT:    v_and_or_b32 v4, v3, s4, 0x800000
+; CHECK-NEXT:    v_and_b32_e32 v8, 0x7ffff, v3
+; CHECK-NEXT:    v_bfe_u32 v9, v3, 20, 3
+; CHECK-NEXT:    v_lshrrev_b32_e32 v3, 19, v3
+; CHECK-NEXT:    v_min_u32_e32 v2, 31, v2
+; CHECK-NEXT:    v_sub_nc_u32_e64 v5, v2, 1 clamp
+; CHECK-NEXT:    v_lshrrev_b32_e32 v7, v2, v4
+; CHECK-NEXT:    v_bfe_u32 v6, v4, 0, v5
+; CHECK-NEXT:    v_lshrrev_b32_e32 v4, v5, v4
+; CHECK-NEXT:    v_cmp_ne_u32_e32 vcc_lo, 0, v6
+; CHECK-NEXT:    v_cndmask_b32_e64 v6, 0, 1, vcc_lo
+; CHECK-NEXT:    v_cmp_ne_u32_e32 vcc_lo, 0, v8
+; CHECK-NEXT:    v_and_or_b32 v5, v7, 1, v6
+; CHECK-NEXT:    v_cndmask_b32_e64 v6, 0, 1, vcc_lo
+; CHECK-NEXT:    v_cmp_ne_u32_e32 vcc_lo, 0, v2
+; CHECK-NEXT:    v_and_b32_e32 v4, v4, v5
+; CHECK-NEXT:    v_and_or_b32 v5, v9, 1, v6
+; CHECK-NEXT:    v_cndmask_b32_e32 v2, 0, v4, vcc_lo
+; CHECK-NEXT:    v_and_b32_e32 v3, v3, v5
+; CHECK-NEXT:    v_add_nc_u32_e32 v2, v7, v2
+; CHECK-NEXT:    v_add_nc_u32_e32 v3, v9, v3
+; CHECK-NEXT:    v_cmp_lt_i32_e32 vcc_lo, 7, v2
+; CHECK-NEXT:    v_cmp_lt_i32_e64 s4, 7, v3
+; CHECK-NEXT:    v_cndmask_b32_e64 v2, v2, 0, vcc_lo
+; CHECK-NEXT:    v_cndmask_b32_e64 v3, v3, 0, s4
+; CHECK-NEXT:    v_cndmask_b32_e64 v4, 0, 8, vcc_lo
+; CHECK-NEXT:    v_add_co_ci_u32_e64 v1, null, 14, v1, s4
+; CHECK-NEXT:    v_or_b32_e32 v2, v4, v2
+; CHECK-NEXT:    v_lshl_or_b32 v3, v1, 3, v3
+; CHECK-NEXT:    v_cmp_gt_i32_e32 vcc_lo, 1, v1
+; CHECK-NEXT:    v_cndmask_b32_e32 v1, v3, v2, vcc_lo
+; CHECK-NEXT:    v_cmp_neq_f32_e32 vcc_lo, 0, v0
+; CHECK-NEXT:    v_cndmask_b32_e32 v1, 0, v1, vcc_lo
+; CHECK-NEXT:    v_cmp_o_f32_e32 vcc_lo, v0, v0
+; CHECK-NEXT:    v_cndmask_b32_e32 v0, 0xff, v1, vcc_lo
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float %x, metadata !"Float8E5M3FNU", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+define float @from_e5m3fnu_1_0() {
+; CHECK-LABEL: from_e5m3fnu_1_0:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 1.0
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 120, metadata !"Float8E5M3FNU")
+  ret float %r
+}
+
+define float @from_e5m3fnu_1_5() {
+; CHECK-LABEL: from_e5m3fnu_1_5:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x3fc00000
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 124, metadata !"Float8E5M3FNU")
+  ret float %r
+}
+
+define float @from_e5m3fnu_2_0() {
+; CHECK-LABEL: from_e5m3fnu_2_0:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 2.0
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 128, metadata !"Float8E5M3FNU")
+  ret float %r
+}
+
+define float @from_e5m3fnu_max_finite() {
+; CHECK-LABEL: from_e5m3fnu_max_finite:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x47e00000
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 254, metadata !"Float8E5M3FNU")
+  ret float %r
+}
+
+define float @from_e5m3fnu_denormal() {
+; CHECK-LABEL: from_e5m3fnu_denormal:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_bfrev_b32_e32 v0, 28
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 4, metadata !"Float8E5M3FNU")
+  ret float %r
+}
+
+define float @from_e5m3fnu_nan() {
+; CHECK-LABEL: from_e5m3fnu_nan:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x7fc00000
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 255, metadata !"Float8E5M3FNU")
+  ret float %r
+}
+
+define float @from_e5m3fnu_zero() {
+; CHECK-LABEL: from_e5m3fnu_zero:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 0, metadata !"Float8E5M3FNU")
+  ret float %r
+}
+
+define float @from_e5m3fnu_dynamic(i8 %x) {
+; CHECK-LABEL: from_e5m3fnu_dynamic:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_and_b32_e32 v0, 0xffff, v0
+; CHECK-NEXT:    v_and_b32_e32 v1, 7, v0
+; CHECK-NEXT:    v_bfe_u32 v0, v0, 3, 5
+; CHECK-NEXT:    v_ffbh_u32_e32 v2, v1
+; CHECK-NEXT:    v_lshlrev_b32_e32 v5, 20, v1
+; CHECK-NEXT:    v_cmp_ne_u32_e32 vcc_lo, 0, v1
+; CHECK-NEXT:    v_cmp_eq_u32_e64 s4, 0, v0
+; CHECK-NEXT:    v_sub_nc_u32_e32 v3, 31, v2
+; CHECK-NEXT:    v_add_nc_u32_e32 v4, -8, v2
+; CHECK-NEXT:    v_sub_nc_u32_e32 v2, 0x8d, v2
+; CHECK-NEXT:    s_and_b32 vcc_lo, s4, vcc_lo
+; CHECK-NEXT:    v_cmp_eq_u32_e64 s4, 31, v0
+; CHECK-NEXT:    v_lshlrev_b32_e64 v3, v3, 1
+; CHECK-NEXT:    v_xor_b32_e32 v3, v1, v3
+; CHECK-NEXT:    v_lshlrev_b32_e32 v3, v4, v3
+; CHECK-NEXT:    v_lshl_or_b32 v4, v0, 23, v5
+; CHECK-NEXT:    v_lshl_or_b32 v2, v2, 23, v3
+; CHECK-NEXT:    v_add_nc_u32_e32 v3, 0x38000000, v4
+; CHECK-NEXT:    v_or_b32_e32 v4, v0, v1
+; CHECK-NEXT:    v_cndmask_b32_e32 v2, v3, v2, vcc_lo
+; CHECK-NEXT:    v_cmp_ne_u32_e64 s5, 0, v4
+; CHECK-NEXT:    v_cmp_eq_u32_e32 vcc_lo, 7, v1
+; CHECK-NEXT:    v_cndmask_b32_e64 v0, 0, v2, s5
+; CHECK-NEXT:    s_and_b32 s4, s4, vcc_lo
+; CHECK-NEXT:    v_cndmask_b32_e64 v0, v0, 0x7fc00000, s4
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 %x, metadata !"Float8E5M3FNU")
+  ret float %r
+}
diff --git a/llvm/test/CodeGen/X86/float-to-arbitrary-fp.ll b/llvm/test/CodeGen/X86/float-to-arbitrary-fp.ll
index 8076f0a11762c..7b01ed05706d3 100644
--- a/llvm/test/CodeGen/X86/float-to-arbitrary-fp.ll
+++ b/llvm/test/CodeGen/X86/float-to-arbitrary-fp.ll
@@ -2406,3 +2406,192 @@ define i8 @to_f8e5m2_from_f64(double %x) {
   %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f64(double %x, metadata !"Float8E5M2", metadata !"round.tonearest", i1 false)
   ret i8 %r
 }
+
+; Float8E5M3FNU
+; Layout: exp(5) mant(3), bias=15, no sign bit
+; Supports: NaN, denormals. No Inf, no signed values.
+
+define i8 @to_f8e5m3fnu_dynamic(float %x) {
+; CHECK-LABEL: to_f8e5m3fnu_dynamic:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    pushq %rax
+; CHECK-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-NEXT:    movd %xmm0, (%rsp) # 4-byte Folded Spill
+; CHECK-NEXT:    leaq {{[0-9]+}}(%rsp), %rdi
+; CHECK-NEXT:    callq frexpf at PLT
+; CHECK-NEXT:    movl {{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT:    movl $7, %ecx
+; CHECK-NEXT:    subl %edx, %ecx
+; CHECK-NEXT:    cmpl $31, %ecx
+; CHECK-NEXT:    movl $31, %eax
+; CHECK-NEXT:    cmovbl %ecx, %eax
+; CHECK-NEXT:    cmpl $1, %eax
+; CHECK-NEXT:    movl %eax, %ecx
+; CHECK-NEXT:    adcl $-1, %ecx
+; CHECK-NEXT:    movd %xmm0, %esi
+; CHECK-NEXT:    movl %esi, %edi
+; CHECK-NEXT:    andl $8388607, %edi # imm = 0x7FFFFF
+; CHECK-NEXT:    leal 8388608(%rdi), %r8d
+; CHECK-NEXT:    movl %r8d, %r9d
+; CHECK-NEXT:    shrl %cl, %r9d
+; CHECK-NEXT:    movl $1, %r10d
+; CHECK-NEXT:    # kill: def $cl killed $cl killed $ecx
+; CHECK-NEXT:    shll %cl, %r10d
+; CHECK-NEXT:    decl %r10d
+; CHECK-NEXT:    xorl %r11d, %r11d
+; CHECK-NEXT:    testl %r10d, %r8d
+; CHECK-NEXT:    setne %r11b
+; CHECK-NEXT:    movl %eax, %ecx
+; CHECK-NEXT:    shrl %cl, %r8d
+; CHECK-NEXT:    movl %r8d, %r10d
+; CHECK-NEXT:    andl $1, %r10d
+; CHECK-NEXT:    orl %r11d, %r10d
+; CHECK-NEXT:    andl %r9d, %r10d
+; CHECK-NEXT:    xorl %ecx, %ecx
+; CHECK-NEXT:    cmpl $1, %eax
+; CHECK-NEXT:    cmovbl %ecx, %r10d
+; CHECK-NEXT:    addl %r8d, %r10d
+; CHECK-NEXT:    xorl %eax, %eax
+; CHECK-NEXT:    cmpl $8, %r10d
+; CHECK-NEXT:    cmovgel %ecx, %r10d
+; CHECK-NEXT:    setge %al
+; CHECK-NEXT:    shll $3, %eax
+; CHECK-NEXT:    orl %r10d, %eax
+; CHECK-NEXT:    shrl $20, %edi
+; CHECK-NEXT:    movl %edi, %r8d
+; CHECK-NEXT:    andl $1, %r8d
+; CHECK-NEXT:    xorl %r9d, %r9d
+; CHECK-NEXT:    testl $524287, %esi # imm = 0x7FFFF
+; CHECK-NEXT:    setne %r9b
+; CHECK-NEXT:    orl %r8d, %r9d
+; CHECK-NEXT:    shrl $19, %esi
+; CHECK-NEXT:    andl %r9d, %esi
+; CHECK-NEXT:    addl %edi, %esi
+; CHECK-NEXT:    xorl %edi, %edi
+; CHECK-NEXT:    cmpl $8, %esi
+; CHECK-NEXT:    cmovgel %ecx, %esi
+; CHECK-NEXT:    setge %dil
+; CHECK-NEXT:    leal (%rdx,%rdi), %r8d
+; CHECK-NEXT:    leal 112(,%r8,8), %r8d
+; CHECK-NEXT:    orl %esi, %r8d
+; CHECK-NEXT:    leal 14(%rdx,%rdi), %edx
+; CHECK-NEXT:    testl %edx, %edx
+; CHECK-NEXT:    cmovlel %eax, %r8d
+; CHECK-NEXT:    pxor %xmm0, %xmm0
+; CHECK-NEXT:    movss (%rsp), %xmm1 # 4-byte Reload
+; CHECK-NEXT:    # xmm1 = mem[0],zero,zero,zero
+; CHECK-NEXT:    ucomiss %xmm0, %xmm1
+; CHECK-NEXT:    cmovnel %r8d, %ecx
+; CHECK-NEXT:    cmovpl %r8d, %ecx
+; CHECK-NEXT:    movl $255, %eax
+; CHECK-NEXT:    cmovnpl %ecx, %eax
+; CHECK-NEXT:    # kill: def $al killed $al killed $eax
+; CHECK-NEXT:    popq %rcx
+; CHECK-NEXT:    .cfi_def_cfa_offset 8
+; CHECK-NEXT:    retq
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(
+      float %x, metadata !"Float8E5M3FNU", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; Saturating: negatives clamp to zero, overflow clamps to max finite.
+define i8 @to_f8e5m3fnu_saturate(float %x) {
+; CHECK-LABEL: to_f8e5m3fnu_saturate:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    subq $40, %rsp
+; CHECK-NEXT:    .cfi_def_cfa_offset 48
+; CHECK-NEXT:    movdqa %xmm0, {{[-0-9]+}}(%r{{[sb]}}p) # 16-byte Spill
+; CHECK-NEXT:    leaq {{[0-9]+}}(%rsp), %rdi
+; CHECK-NEXT:    callq frexpf at PLT
+; CHECK-NEXT:    movl {{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT:    movl $7, %ecx
+; CHECK-NEXT:    subl %edx, %ecx
+; CHECK-NEXT:    cmpl $31, %ecx
+; CHECK-NEXT:    movl $31, %eax
+; CHECK-NEXT:    cmovbl %ecx, %eax
+; CHECK-NEXT:    cmpl $1, %eax
+; CHECK-NEXT:    movl %eax, %ecx
+; CHECK-NEXT:    adcl $-1, %ecx
+; CHECK-NEXT:    movd %xmm0, %esi
+; CHECK-NEXT:    movl %esi, %edi
+; CHECK-NEXT:    andl $8388607, %edi # imm = 0x7FFFFF
+; CHECK-NEXT:    leal 8388608(%rdi), %r8d
+; CHECK-NEXT:    movl %r8d, %r9d
+; CHECK-NEXT:    shrl %cl, %r9d
+; CHECK-NEXT:    movl $1, %r10d
+; CHECK-NEXT:    # kill: def $cl killed $cl killed $ecx
+; CHECK-NEXT:    shll %cl, %r10d
+; CHECK-NEXT:    decl %r10d
+; CHECK-NEXT:    xorl %r11d, %r11d
+; CHECK-NEXT:    testl %r10d, %r8d
+; CHECK-NEXT:    setne %r11b
+; CHECK-NEXT:    movl %eax, %ecx
+; CHECK-NEXT:    shrl %cl, %r8d
+; CHECK-NEXT:    movl %r8d, %r10d
+; CHECK-NEXT:    andl $1, %r10d
+; CHECK-NEXT:    orl %r11d, %r10d
+; CHECK-NEXT:    andl %r9d, %r10d
+; CHECK-NEXT:    xorl %ecx, %ecx
+; CHECK-NEXT:    cmpl $1, %eax
+; CHECK-NEXT:    cmovbl %ecx, %r10d
+; CHECK-NEXT:    addl %r8d, %r10d
+; CHECK-NEXT:    xorl %eax, %eax
+; CHECK-NEXT:    cmpl $8, %r10d
+; CHECK-NEXT:    cmovgel %ecx, %r10d
+; CHECK-NEXT:    setge %al
+; CHECK-NEXT:    shll $3, %eax
+; CHECK-NEXT:    orl %r10d, %eax
+; CHECK-NEXT:    shrl $20, %edi
+; CHECK-NEXT:    movl %edi, %r8d
+; CHECK-NEXT:    andl $1, %r8d
+; CHECK-NEXT:    xorl %r9d, %r9d
+; CHECK-NEXT:    testl $524287, %esi # imm = 0x7FFFF
+; CHECK-NEXT:    setne %r9b
+; CHECK-NEXT:    orl %r8d, %r9d
+; CHECK-NEXT:    shrl $19, %esi
+; CHECK-NEXT:    andl %r9d, %esi
+; CHECK-NEXT:    addl %edi, %esi
+; CHECK-NEXT:    xorl %edi, %edi
+; CHECK-NEXT:    cmpl $8, %esi
+; CHECK-NEXT:    cmovgel %ecx, %esi
+; CHECK-NEXT:    setge %dil
+; CHECK-NEXT:    leal (%rdx,%rdi), %r8d
+; CHECK-NEXT:    leal 112(,%r8,8), %r8d
+; CHECK-NEXT:    orl %esi, %r8d
+; CHECK-NEXT:    leal 14(%rdx,%rdi), %edx
+; CHECK-NEXT:    testl %edx, %edx
+; CHECK-NEXT:    cmovlel %eax, %r8d
+; CHECK-NEXT:    cmpl $7, %esi
+; CHECK-NEXT:    setge %al
+; CHECK-NEXT:    cmpl $31, %edx
+; CHECK-NEXT:    sete %sil
+; CHECK-NEXT:    andb %al, %sil
+; CHECK-NEXT:    cmpl $32, %edx
+; CHECK-NEXT:    setge %al
+; CHECK-NEXT:    orb %sil, %al
+; CHECK-NEXT:    movl $254, %edx
+; CHECK-NEXT:    cmovnel %edx, %r8d
+; CHECK-NEXT:    pxor %xmm0, %xmm0
+; CHECK-NEXT:    movaps {{[-0-9]+}}(%r{{[sb]}}p), %xmm2 # 16-byte Reload
+; CHECK-NEXT:    ucomiss %xmm0, %xmm2
+; CHECK-NEXT:    movl $0, %eax
+; CHECK-NEXT:    cmovnel %r8d, %eax
+; CHECK-NEXT:    cmovpl %r8d, %eax
+; CHECK-NEXT:    movaps {{.*#+}} xmm1 = [NaN,NaN,NaN,NaN]
+; CHECK-NEXT:    andps %xmm2, %xmm1
+; CHECK-NEXT:    ucomiss {{\.?LCPI[0-9]+_[0-9]+}}(%rip), %xmm1
+; CHECK-NEXT:    cmovnel %eax, %edx
+; CHECK-NEXT:    cmovpl %eax, %edx
+; CHECK-NEXT:    ucomiss %xmm2, %xmm0
+; CHECK-NEXT:    cmoval %ecx, %edx
+; CHECK-NEXT:    ucomiss %xmm0, %xmm2
+; CHECK-NEXT:    movl $255, %eax
+; CHECK-NEXT:    cmovnpl %edx, %eax
+; CHECK-NEXT:    # kill: def $al killed $al killed $eax
+; CHECK-NEXT:    addq $40, %rsp
+; CHECK-NEXT:    .cfi_def_cfa_offset 8
+; CHECK-NEXT:    retq
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(
+      float %x, metadata !"Float8E5M3FNU", metadata !"round.tonearest", i1 true)
+  ret i8 %r
+}
diff --git a/llvm/unittests/ADT/APFloatTest.cpp b/llvm/unittests/ADT/APFloatTest.cpp
index 0c5103c4b49e4..4e0deddb9ba8a 100644
--- a/llvm/unittests/ADT/APFloatTest.cpp
+++ b/llvm/unittests/ADT/APFloatTest.cpp
@@ -10783,6 +10783,8 @@ TEST(APFloatTest, getArbitraryFPSemantics) {
             APFloat::getArbitraryFPSemantics("Float8E5M2"));
   EXPECT_EQ(&APFloat::Float8E4M3FN(),
             APFloat::getArbitraryFPSemantics("Float8E4M3FN"));
+  EXPECT_EQ(&APFloat::Float8E5M3FNU(),
+            APFloat::getArbitraryFPSemantics("Float8E5M3FNU"));
   EXPECT_EQ(&APFloat::Float6E3M2FN(),
             APFloat::getArbitraryFPSemantics("Float6E3M2FN"));
   EXPECT_EQ(&APFloat::Float6E2M3FN(),
@@ -10797,7 +10799,6 @@ TEST(APFloatTest, getArbitraryFPSemantics) {
   EXPECT_EQ(nullptr, APFloat::getArbitraryFPSemantics("Float8E4M3B11FNUZ"));
   EXPECT_EQ(nullptr, APFloat::getArbitraryFPSemantics("Float8E3M4"));
   EXPECT_EQ(nullptr, APFloat::getArbitraryFPSemantics("Float8E8M0FNU"));
-  EXPECT_EQ(nullptr, APFloat::getArbitraryFPSemantics("Float8E5M3FNU"));
 
   // Invalid formats report no semantics.
   EXPECT_EQ(nullptr, APFloat::getArbitraryFPSemantics(""));



More information about the llvm-branch-commits mailing list