[llvm] 4e95be7 - [RFC][SPIR-V] Add intrinsics to convert to/from ap.float (#164252)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jan 14 07:53:57 PST 2026
Author: Dmitry Sidorov
Date: 2026-01-14T16:53:53+01:00
New Revision: 4e95be704302561be0b217eda732624e8e96a906
URL: https://github.com/llvm/llvm-project/commit/4e95be704302561be0b217eda732624e8e96a906
DIFF: https://github.com/llvm/llvm-project/commit/4e95be704302561be0b217eda732624e8e96a906.diff
LOG: [RFC][SPIR-V] Add intrinsics to convert to/from ap.float (#164252)
The patch adds two intrinsics: llvm.convert.to.arbitrary.fp and
llvm.convert.from.arbitrary.fp.
The intrinsics perform conversions between values whose interpretation
differs from their representation in LLVM IR. The intrinsics are
overloaded on both its return type and first argument. Metadata operands
describe how the raw bits should be interpreted before and after the
conversion.
Typical use case is to convert IEEE-754 floating point types to FP8/FP4
and backwards for ML applications.
Addresses
https://discourse.llvm.org/t/rfc-spir-v-way-to-represent-float8-in-llvm-ir/87758/10
Added:
llvm/test/Assembler/arbitrary-fp-convert.ll
llvm/test/Verifier/arbitrary-fp-convert.ll
Modified:
llvm/docs/LangRef.rst
llvm/include/llvm/ADT/APFloat.h
llvm/include/llvm/IR/Intrinsics.td
llvm/lib/IR/Verifier.cpp
llvm/lib/Support/APFloat.cpp
llvm/unittests/ADT/APFloatTest.cpp
Removed:
################################################################################
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 39c74128de6c8..a6881099f81f4 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -21715,6 +21715,164 @@ environment <floatenv>` *except* for the rounding mode.
This intrinsic is not supported on all targets. Some targets may not support
all rounding modes.
+'``llvm.convert.to.arbitrary.fp``' Intrinsic
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Syntax:
+"""""""
+
+::
+
+ declare <iNxM> @llvm.convert.to.arbitrary.fp.<iNxM>.<fNxM>(
+ <fNxM> <value>, metadata <interpretation>,
+ metadata <rounding mode>, i1 <saturation>)
+
+Overview:
+"""""""""
+
+The ``llvm.convert.to.arbitrary.fp`` intrinsic converts a native LLVM
+floating-point value to an arbitrary FP format, returning the result as an integer
+containing the arbitrary FP bits. This intrinsic is overloaded on both its return
+type and first argument.
+
+Arguments:
+""""""""""
+
+``value``
+ The native LLVM floating-point value to convert (e.g., ``half``, ``float``, ``double``).
+
+``interpretation``
+ A metadata string describing the target arbitrary FP format. Supported format names include:
+
+ - FP8 formats: ``"Float8E5M2"``, ``"Float8E5M2FNUZ"``, ``"Float8E4M3"``,
+ ``"Float8E4M3FN"``, ``"Float8E4M3FNUZ"``, ``"Float8E4M3B11FNUZ"``, ``"Float8E3M4"``,
+ ``"Float8E8M0FNU"``
+ - FP6 formats: ``"Float6E3M2FN"``, ``"Float6E2M3FN"``
+ - FP4 formats: ``"Float4E2M1FN"``
+
+``rounding mode``
+ A metadata string specifying the rounding mode. The permitted strings match those
+ accepted by ``llvm.fptrunc.round`` (for example,
+ ``"round.tonearest"`` or ``"round.towardzero"``).
+
+ The rounding mode is only consulted when ``value`` is not exactly representable in the target format.
+ If the value is exactly representable, all rounding modes produce the same result.
+
+``saturation``
+ A compile-time constant boolean value (``i1``). This parameter controls how overflow is handled
+ when values exceed the representable finite range of the target format:
+
+ - When ``true``: overflowing values are clamped to the minimum or maximum representable finite value
+ (saturating to the largest negative finite value or largest positive finite value).
+ - When ``false``: overflowing values are converted to infinity (preserving sign of the original value) if the
+ target format supports infinity, or return a poison value if infinity is not supported
+ by the target format.
+
+ This parameter must be an immediate constant.
+
+Semantics:
+""""""""""
+
+The intrinsic converts the native LLVM floating-point value to the arbitrary FP
+format specified by ``interpretation``, applying the requested rounding mode and
+saturation behavior. The conversion is performed in two steps: first, the value is
+rounded according to the specified rounding mode to fit the target format's precision;
+then, if the rounded result exceeds the target format's representable range, saturation
+is applied according to the ``saturation`` parameter. The result is returned as an
+integer (e.g., ``i8`` for FP8, ``i6`` for FP6) containing the encoded arbitrary FP bits.
+
+**Handling of special values:**
+
+- **NaN**: NaN values follow LLVM's standard :ref:`NaN rules <floatnan>`. When the target
+ format supports NaN, the NaN representation is preserved (quiet NaNs remain quiet, signaling
+ NaNs remain signaling). The exact NaN payload may be truncated or extended to fit the target
+ format's payload size. If the target format does not support NaN, the intrinsic returns a
+ poison value.
+- **Infinity and Overflow**: If the input is +/-Inf or a finite value that exceeds the representable range:
+
+ - When ``saturation`` is ``false`` and the target format supports infinity, the result is +/-Inf (preserving the sign).
+ - When ``saturation`` is ``false`` and the target format does not support infinity (e.g., formats
+ with "FN" suffix), the intrinsic returns a poison value.
+ - When ``saturation`` is ``true``, the value is clamped to the maximum/minimum representable finite value.
+
+For FP6/FP4 interpretations, producers are expected to use ``saturation`` = ``true``; using ``saturation`` = ``false`` and generating NaN/Inf/overflowing values results in a poison value.
+
+Example:
+""""""""
+
+::
+
+ ; Convert half to FP8 E4M3 format
+ %fp8bits = call i8 @llvm.convert.to.arbitrary.fp.i8.f16(
+ half %value, metadata !"Float8E4M3",
+ metadata !"round.tonearest", i1 false)
+
+ ; Convert vector of float to FP8 E5M2 with saturation
+ %vec_fp8 = call <4 x i8> @llvm.convert.to.arbitrary.fp.v4i8.v4f32(
+ <4 x float> %values, metadata !"Float8E5M2",
+ metadata !"round.towardzero", i1 true)
+
+'``llvm.convert.from.arbitrary.fp``' Intrinsic
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Syntax:
+"""""""
+
+::
+
+ declare <fNxM> @llvm.convert.from.arbitrary.fp.<fNxM>.<iNxM>(
+ <iNxM> <value>, metadata <interpretation>)
+
+Overview:
+"""""""""
+
+The ``llvm.convert.from.arbitrary.fp`` intrinsic converts an integer containing
+arbitrary FP bits to a native LLVM floating-point value. This intrinsic is
+overloaded on both its return type and first argument.
+
+Arguments:
+""""""""""
+
+``value``
+ An integer value containing the arbitrary FP bits (e.g., ``i8`` for FP8, ``i6`` for FP6).
+
+``interpretation``
+ A metadata string describing the source arbitrary FP format. Supported format names include:
+
+ - FP8 formats: ``"Float8E5M2"``, ``"Float8E5M2FNUZ"``, ``"Float8E4M3"``,
+ ``"Float8E4M3FN"``, ``"Float8E4M3FNUZ"``, ``"Float8E4M3B11FNUZ"``, ``"Float8E3M4"``,
+ ``"Float8E8M0FNU"``
+ - FP6 formats: ``"Float6E3M2FN"``, ``"Float6E2M3FN"``
+ - FP4 formats: ``"Float4E2M1FN"``
+
+Semantics:
+""""""""""
+
+The intrinsic interprets the integer value as arbitrary FP bits according to
+``interpretation``, then converts to the native LLVM floating-point result type.
+
+Conversions from arbitrary FP formats to native LLVM floating-point types are
+widening conversions (e.g., FP8 to FP16 or FP32), which are exact and require no rounding.
+Normal finite values are converted exactly. NaN values follow LLVM's standard :ref:`NaN rules
+<floatnan>`; the NaN representation is preserved (quiet NaNs remain quiet, signaling NaNs
+remain signaling), and the NaN payload may be truncated or extended to fit the target format's
+payload size. Infinity values are preserved as infinity. If a value exceeds the representable
+range of the target type (for example, converting ``Float8E8M0FNU`` with large exponents to
+``half``), the result is converted to infinity with the appropriate sign.
+
+Example:
+""""""""
+
+::
+
+ ; Convert FP8 E4M3 bits to half
+ %half_val = call half @llvm.convert.from.arbitrary.fp.f16.i8(
+ i8 %fp8bits, metadata !"Float8E4M3")
+
+ ; Convert vector of FP8 E5M2 bits to float
+ %vec_float = call <4 x float> @llvm.convert.from.arbitrary.fp.v4f32.v4i8(
+ <4 x i8> %fp8_values, metadata !"Float8E5M2")
+
Convergence Intrinsics
----------------------
diff --git a/llvm/include/llvm/ADT/APFloat.h b/llvm/include/llvm/ADT/APFloat.h
index 17c852e957617..44fa3919962c4 100644
--- a/llvm/include/llvm/ADT/APFloat.h
+++ b/llvm/include/llvm/ADT/APFloat.h
@@ -407,6 +407,11 @@ class APFloatBase {
/// Returns the size of the floating point number (in bits) in the given
/// semantics.
LLVM_ABI static unsigned getSizeInBits(const fltSemantics &Sem);
+
+ /// Returns true if the given string is a valid arbitrary floating-point
+ /// format interpretation for llvm.convert.to.arbitrary.fp and
+ /// llvm.convert.from.arbitrary.fp intrinsics.
+ LLVM_ABI static bool isValidArbitraryFPFormat(StringRef Format);
};
namespace detail {
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index 1c63b4eb90578..24d51bdaf67fb 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -1133,6 +1133,22 @@ let IntrProperties = [IntrNoMem, IntrSpeculatable, IntrNoCreateUndefOrPoison] in
def int_fptrunc_round : DefaultAttrsIntrinsic<[ llvm_anyfloat_ty ],
[ llvm_anyfloat_ty, llvm_metadata_ty ]>;
+ // Convert from native LLVM floating-point to arbitrary FP format
+ // Returns an integer containing the arbitrary FP bits
+ def int_convert_to_arbitrary_fp
+ : DefaultAttrsIntrinsic<
+ [ llvm_anyint_ty ],
+ [ llvm_anyfloat_ty, llvm_metadata_ty, llvm_metadata_ty, llvm_i1_ty ],
+ [ IntrNoMem, IntrSpeculatable, ImmArg<ArgIndex<3>> ]>;
+
+ // Convert from arbitrary FP format to native LLVM floating-point
+ // Takes an integer containing the arbitrary FP bits
+ def int_convert_from_arbitrary_fp
+ : DefaultAttrsIntrinsic<
+ [ llvm_anyfloat_ty ],
+ [ llvm_anyint_ty, llvm_metadata_ty ],
+ [ IntrNoMem, IntrSpeculatable ]>;
+
def int_canonicalize : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], [LLVMMatchType<0>],
[IntrNoMem]>;
// Arithmetic fence intrinsic.
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index bb552861130d2..38da185226a92 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -79,6 +79,7 @@
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/EHPersonalities.h"
+#include "llvm/IR/FPEnv.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GCStrategy.h"
#include "llvm/IR/GlobalAlias.h"
@@ -5972,6 +5973,81 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) {
"unsupported rounding mode argument", Call);
break;
}
+ case Intrinsic::convert_to_arbitrary_fp: {
+ // Check that vector element counts are consistent.
+ Type *ValueTy = Call.getArgOperand(0)->getType();
+ Type *IntTy = Call.getType();
+
+ if (auto *ValueVecTy = dyn_cast<VectorType>(ValueTy)) {
+ auto *IntVecTy = dyn_cast<VectorType>(IntTy);
+ Check(IntVecTy,
+ "if floating-point operand is a vector, integer operand must also "
+ "be a vector",
+ Call);
+ Check(ValueVecTy->getElementCount() == IntVecTy->getElementCount(),
+ "floating-point and integer vector operands must have the same "
+ "element count",
+ Call);
+ }
+
+ // Check interpretation metadata (argoperand 1).
+ auto *InterpMAV = dyn_cast<MetadataAsValue>(Call.getArgOperand(1));
+ Check(InterpMAV, "missing interpretation metadata operand", Call);
+ auto *InterpStr = dyn_cast<MDString>(InterpMAV->getMetadata());
+ Check(InterpStr, "interpretation metadata operand must be a string", Call);
+ StringRef Interp = InterpStr->getString();
+
+ Check(!Interp.empty(), "interpretation metadata string must not be empty",
+ Call);
+
+ // Valid interpretation strings: mini-float format names.
+ Check(APFloatBase::isValidArbitraryFPFormat(Interp),
+ "unsupported interpretation metadata string", Call);
+
+ // Check rounding mode metadata (argoperand 2).
+ auto *RoundingMAV = dyn_cast<MetadataAsValue>(Call.getArgOperand(2));
+ Check(RoundingMAV, "missing rounding mode metadata operand", Call);
+ auto *RoundingStr = dyn_cast<MDString>(RoundingMAV->getMetadata());
+ Check(RoundingStr, "rounding mode metadata operand must be a string", Call);
+
+ std::optional<RoundingMode> RM =
+ convertStrToRoundingMode(RoundingStr->getString());
+ Check(RM && *RM != RoundingMode::Dynamic,
+ "unsupported rounding mode argument", Call);
+ break;
+ }
+ case Intrinsic::convert_from_arbitrary_fp: {
+ // Check that vector element counts are consistent.
+ Type *IntTy = Call.getArgOperand(0)->getType();
+ Type *ValueTy = Call.getType();
+
+ if (auto *ValueVecTy = dyn_cast<VectorType>(ValueTy)) {
+ auto *IntVecTy = dyn_cast<VectorType>(IntTy);
+ Check(IntVecTy,
+ "if floating-point operand is a vector, integer operand must also "
+ "be a vector",
+ Call);
+ Check(ValueVecTy->getElementCount() == IntVecTy->getElementCount(),
+ "floating-point and integer vector operands must have the same "
+ "element count",
+ Call);
+ }
+
+ // Check interpretation metadata (argoperand 1).
+ auto *InterpMAV = dyn_cast<MetadataAsValue>(Call.getArgOperand(1));
+ Check(InterpMAV, "missing interpretation metadata operand", Call);
+ auto *InterpStr = dyn_cast<MDString>(InterpMAV->getMetadata());
+ Check(InterpStr, "interpretation metadata operand must be a string", Call);
+ StringRef Interp = InterpStr->getString();
+
+ Check(!Interp.empty(), "interpretation metadata string must not be empty",
+ Call);
+
+ // Valid interpretation strings: mini-float format names.
+ Check(APFloatBase::isValidArbitraryFPFormat(Interp),
+ "unsupported interpretation metadata string", Call);
+ break;
+ }
#define BEGIN_REGISTER_VP_INTRINSIC(VPID, ...) case Intrinsic::VPID:
#include "llvm/IR/VPIntrinsics.def"
#undef BEGIN_REGISTER_VP_INTRINSIC
diff --git a/llvm/lib/Support/APFloat.cpp b/llvm/lib/Support/APFloat.cpp
index dc8526fb5a8c8..b359c680ab673 100644
--- a/llvm/lib/Support/APFloat.cpp
+++ b/llvm/lib/Support/APFloat.cpp
@@ -6077,6 +6077,14 @@ float APFloat::convertToFloat() const {
return Temp.getIEEE().convertToFloat();
}
+bool APFloatBase::isValidArbitraryFPFormat(StringRef Format) {
+ static constexpr StringLiteral ValidFormats[] = {
+ "Float8E5M2", "Float8E5M2FNUZ", "Float8E4M3", "Float8E4M3FN",
+ "Float8E4M3FNUZ", "Float8E4M3B11FNUZ", "Float8E3M4", "Float8E8M0FNU",
+ "Float6E3M2FN", "Float6E2M3FN", "Float4E2M1FN"};
+ return llvm::is_contained(ValidFormats, Format);
+}
+
APFloat::Storage::~Storage() {
if (usesLayout<IEEEFloat>(*semantics)) {
IEEE.~IEEEFloat();
diff --git a/llvm/test/Assembler/arbitrary-fp-convert.ll b/llvm/test/Assembler/arbitrary-fp-convert.ll
new file mode 100644
index 0000000000000..1c29c85543921
--- /dev/null
+++ b/llvm/test/Assembler/arbitrary-fp-convert.ll
@@ -0,0 +1,169 @@
+; RUN: llvm-as < %s | llvm-dis | llvm-as | llvm-dis | FileCheck %s
+; Test valid scalar and vector conversions for arbitrary FP intrinsics
+
+declare i8 @llvm.convert.to.arbitrary.fp.i8.f16(half, metadata, metadata, i1)
+declare <4 x i8> @llvm.convert.to.arbitrary.fp.v4i8.v4f16(<4 x half>, metadata, metadata, i1)
+declare <8 x i8> @llvm.convert.to.arbitrary.fp.v8i8.v8f16(<8 x half>, metadata, metadata, i1)
+declare <4 x i8> @llvm.convert.to.arbitrary.fp.v4i8.v4f32(<4 x float>, metadata, metadata, i1)
+
+declare half @llvm.convert.from.arbitrary.fp.f16.i8(i8, metadata)
+declare <4 x half> @llvm.convert.from.arbitrary.fp.v4f16.v4i8(<4 x i8>, metadata)
+declare <8 x half> @llvm.convert.from.arbitrary.fp.v8f16.v8i8(<8 x i8>, metadata)
+declare float @llvm.convert.from.arbitrary.fp.f32.i8(i8, metadata)
+declare <4 x float> @llvm.convert.from.arbitrary.fp.v4f32.v4i8(<4 x i8>, metadata)
+
+; Scalar conversions to arbitrary FP
+; CHECK-LABEL: @good_half_to_fp8(
+define i8 @good_half_to_fp8(half %v) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f16(
+ half %v, metadata !"Float8E5M2", metadata !"round.towardzero", i1 true)
+ ret i8 %r
+}
+
+; CHECK-LABEL: @good_half_to_fp8_fnuz(
+define i8 @good_half_to_fp8_fnuz(half %v) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f16(
+ half %v, metadata !"Float8E4M3FNUZ", metadata !"round.tonearest", i1 false)
+ ret i8 %r
+}
+
+; CHECK-LABEL: @good_half_to_fp8_fn(
+define i8 @good_half_to_fp8_fn(half %v) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f16(
+ half %v, metadata !"Float8E4M3FN", metadata !"round.tonearest", i1 false)
+ ret i8 %r
+}
+
+; Scalar conversions from arbitrary FP
+; CHECK-LABEL: @good_fp8_to_half(
+define half @good_fp8_to_half(i8 %v) {
+ %r = call half @llvm.convert.from.arbitrary.fp.f16.i8(
+ i8 %v, metadata !"Float8E4M3")
+ ret half %r
+}
+
+; CHECK-LABEL: @good_fp8_e5m2_to_half(
+define half @good_fp8_e5m2_to_half(i8 %v) {
+ %r = call half @llvm.convert.from.arbitrary.fp.f16.i8(
+ i8 %v, metadata !"Float8E5M2")
+ ret half %r
+}
+
+; CHECK-LABEL: @good_fp8_to_float(
+define float @good_fp8_to_float(i8 %v) {
+ %r = call float @llvm.convert.from.arbitrary.fp.f32.i8(
+ i8 %v, metadata !"Float8E4M3")
+ ret float %r
+}
+
+; Vector conversions to arbitrary FP
+; CHECK-LABEL: @good_vec4_half_to_fp8(
+define <4 x i8> @good_vec4_half_to_fp8(<4 x half> %v) {
+ %r = call <4 x i8> @llvm.convert.to.arbitrary.fp.v4i8.v4f16(
+ <4 x half> %v, metadata !"Float8E4M3FN", metadata !"round.towardzero", i1 true)
+ ret <4 x i8> %r
+}
+
+; CHECK-LABEL: @good_vec8_half_to_fp8(
+define <8 x i8> @good_vec8_half_to_fp8(<8 x half> %v) {
+ %r = call <8 x i8> @llvm.convert.to.arbitrary.fp.v8i8.v8f16(
+ <8 x half> %v, metadata !"Float8E5M2FNUZ", metadata !"round.tonearest", i1 false)
+ ret <8 x i8> %r
+}
+
+; CHECK-LABEL: @good_vec4_float_to_fp8(
+define <4 x i8> @good_vec4_float_to_fp8(<4 x float> %v) {
+ %r = call <4 x i8> @llvm.convert.to.arbitrary.fp.v4i8.v4f32(
+ <4 x float> %v, metadata !"Float8E4M3B11FNUZ", metadata !"round.tonearest", i1 false)
+ ret <4 x i8> %r
+}
+
+; Vector conversions from arbitrary FP
+; CHECK-LABEL: @good_vec4_fp8_to_half(
+define <4 x half> @good_vec4_fp8_to_half(<4 x i8> %v) {
+ %r = call <4 x half> @llvm.convert.from.arbitrary.fp.v4f16.v4i8(
+ <4 x i8> %v, metadata !"Float8E4M3")
+ ret <4 x half> %r
+}
+
+; CHECK-LABEL: @good_vec8_fp8_to_half(
+define <8 x half> @good_vec8_fp8_to_half(<8 x i8> %v) {
+ %r = call <8 x half> @llvm.convert.from.arbitrary.fp.v8f16.v8i8(
+ <8 x i8> %v, metadata !"Float8E5M2")
+ ret <8 x half> %r
+}
+
+; CHECK-LABEL: @good_vec4_fp8_to_float(
+define <4 x float> @good_vec4_fp8_to_float(<4 x i8> %v) {
+ %r = call <4 x float> @llvm.convert.from.arbitrary.fp.v4f32.v4i8(
+ <4 x i8> %v, metadata !"Float8E4M3B11FNUZ")
+ ret <4 x float> %r
+}
+
+; Test
diff erent rounding modes
+; CHECK-LABEL: @good_rounding_towardzero(
+define i8 @good_rounding_towardzero(half %v) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f16(
+ half %v, metadata !"Float8E4M3", metadata !"round.towardzero", i1 false)
+ ret i8 %r
+}
+
+; CHECK-LABEL: @good_rounding_upward(
+define i8 @good_rounding_upward(half %v) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f16(
+ half %v, metadata !"Float8E4M3", metadata !"round.upward", i1 false)
+ ret i8 %r
+}
+
+; CHECK-LABEL: @good_rounding_downward(
+define i8 @good_rounding_downward(half %v) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f16(
+ half %v, metadata !"Float8E4M3", metadata !"round.downward", i1 false)
+ ret i8 %r
+}
+
+; Test all supported formats
+; CHECK-LABEL: @good_float8_e5m2_fnuz(
+define i8 @good_float8_e5m2_fnuz(half %v) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f16(
+ half %v, metadata !"Float8E5M2FNUZ", metadata !"round.tonearest", i1 false)
+ ret i8 %r
+}
+
+; CHECK-LABEL: @good_float8_e3m4(
+define i8 @good_float8_e3m4(half %v) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f16(
+ half %v, metadata !"Float8E3M4", metadata !"round.tonearest", i1 false)
+ ret i8 %r
+}
+
+; CHECK-LABEL: @good_float8_e8m0fnu(
+define i8 @good_float8_e8m0fnu(half %v) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f16(
+ half %v, metadata !"Float8E8M0FNU", metadata !"round.tonearest", i1 false)
+ ret i8 %r
+}
+
+; CHECK-LABEL: @good_float6_e3m2fn(
+define i6 @good_float6_e3m2fn(half %v) {
+ %r = call i6 @llvm.convert.to.arbitrary.fp.i6.f16(
+ half %v, metadata !"Float6E3M2FN", metadata !"round.tonearest", i1 false)
+ ret i6 %r
+}
+
+; CHECK-LABEL: @good_float6_e2m3fn(
+define i6 @good_float6_e2m3fn(half %v) {
+ %r = call i6 @llvm.convert.to.arbitrary.fp.i6.f16(
+ half %v, metadata !"Float6E2M3FN", metadata !"round.tonearest", i1 false)
+ ret i6 %r
+}
+
+; CHECK-LABEL: @good_float4_e2m1fn(
+define i4 @good_float4_e2m1fn(half %v) {
+ %r = call i4 @llvm.convert.to.arbitrary.fp.i4.f16(
+ half %v, metadata !"Float4E2M1FN", metadata !"round.tonearest", i1 false)
+ ret i4 %r
+}
+
+declare i6 @llvm.convert.to.arbitrary.fp.i6.f16(half, metadata, metadata, i1)
+declare i4 @llvm.convert.to.arbitrary.fp.i4.f16(half, metadata, metadata, i1)
diff --git a/llvm/test/Verifier/arbitrary-fp-convert.ll b/llvm/test/Verifier/arbitrary-fp-convert.ll
new file mode 100644
index 0000000000000..9e3487c9c80b9
--- /dev/null
+++ b/llvm/test/Verifier/arbitrary-fp-convert.ll
@@ -0,0 +1,124 @@
+;; Test verification of arbitrary FP conversion intrinsics:
+;; - Metadata validation (interpretation, rounding mode)
+;; - Type checking (pointer types, integer types, vector mismatches)
+; RUN: split-file %s %t
+; RUN: not llvm-as %t/bad-interpretation-empty.ll -disable-output 2>&1 | FileCheck %s --check-prefix=BAD-INTERP-EMPTY
+; RUN: not llvm-as %t/bad-interpretation-unknown.ll -disable-output 2>&1 | FileCheck %s --check-prefix=BAD-INTERP-UNKNOWN
+; RUN: not llvm-as %t/bad-rounding.ll -disable-output 2>&1 | FileCheck %s --check-prefix=BAD-ROUNDING
+; RUN: not opt -S -passes=verify %t/ptr-to-arbitrary-fp.ll 2>&1 | FileCheck %s --check-prefix=PTR-TO-FP
+; RUN: not opt -S -passes=verify %t/arbitrary-fp-to-ptr.ll 2>&1 | FileCheck %s --check-prefix=FP-TO-PTR
+; RUN: not opt -S -passes=verify %t/int-to-arbitrary-fp.ll 2>&1 | FileCheck %s --check-prefix=INT-TO-FP
+; RUN: not opt -S -passes=verify %t/arbitrary-fp-to-int.ll 2>&1 | FileCheck %s --check-prefix=FP-TO-INT
+; RUN: not opt -S -passes=verify %t/vec-ptr-to-arbitrary-fp.ll 2>&1 | FileCheck %s --check-prefix=VEC-PTR-TO-FP
+; RUN: not opt -S -passes=verify %t/vec-to-scalar-mismatch.ll 2>&1 | FileCheck %s --check-prefix=VEC-SCALAR-MISMATCH
+; RUN: not opt -S -passes=verify %t/vec-size-mismatch.ll 2>&1 | FileCheck %s --check-prefix=VEC-SIZE-MISMATCH
+
+;--- bad-interpretation-empty.ll
+; BAD-INTERP-EMPTY: interpretation metadata string must not be empty
+
+declare i8 @llvm.convert.to.arbitrary.fp.i8.f16(half, metadata, metadata, i1)
+
+define i8 @bad_interpretation_empty(half %v) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f16(
+ half %v, metadata !"", metadata !"round.tonearest", i1 false)
+ ret i8 %r
+}
+
+;--- bad-interpretation-unknown.ll
+; BAD-INTERP-UNKNOWN: unsupported interpretation metadata string
+
+declare i8 @llvm.convert.to.arbitrary.fp.i8.f16(half, metadata, metadata, i1)
+
+define i8 @bad_interpretation_unknown(half %v) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f16(
+ half %v, metadata !"unknown", metadata !"round.tonearest", i1 false)
+ ret i8 %r
+}
+
+;--- bad-rounding.ll
+; BAD-ROUNDING: unsupported rounding mode argument
+
+declare i8 @llvm.convert.to.arbitrary.fp.i8.f16(half, metadata, metadata, i1)
+
+define i8 @bad_rounding(half %v) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f16(
+ half %v, metadata !"Float8E4M3", metadata !"round.dynamic", i1 false)
+ ret i8 %r
+}
+
+;--- ptr-to-arbitrary-fp.ll
+; PTR-TO-FP: Intrinsic has incorrect argument type!
+
+declare i8 @llvm.convert.to.arbitrary.fp.i8.ptr(ptr, metadata, metadata, i1)
+
+define i8 @bad_ptr_to_fp(ptr %p) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.ptr(
+ ptr %p, metadata !"Float8E4M3", metadata !"round.tonearest", i1 false)
+ ret i8 %r
+}
+
+;--- arbitrary-fp-to-ptr.ll
+; FP-TO-PTR: Intrinsic has incorrect return type!
+
+declare ptr @llvm.convert.from.arbitrary.fp.ptr.i8(i8, metadata)
+
+define ptr @bad_fp_to_ptr(i8 %v) {
+ %r = call ptr @llvm.convert.from.arbitrary.fp.ptr.i8(
+ i8 %v, metadata !"Float8E4M3")
+ ret ptr %r
+}
+
+;--- int-to-arbitrary-fp.ll
+; INT-TO-FP: Intrinsic has incorrect argument type!
+
+declare i8 @llvm.convert.to.arbitrary.fp.i8.i32(i32, metadata, metadata, i1)
+
+define i8 @bad_int_to_fp(i32 %v) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.i32(
+ i32 %v, metadata !"Float8E4M3", metadata !"round.tonearest", i1 false)
+ ret i8 %r
+}
+
+;--- arbitrary-fp-to-int.ll
+; FP-TO-INT: Intrinsic has incorrect return type!
+
+declare i32 @llvm.convert.from.arbitrary.fp.i32.i8(i8, metadata)
+
+define i32 @bad_fp_to_int(i8 %v) {
+ %r = call i32 @llvm.convert.from.arbitrary.fp.i32.i8(
+ i8 %v, metadata !"Float8E4M3")
+ ret i32 %r
+}
+
+;--- vec-ptr-to-arbitrary-fp.ll
+; VEC-PTR-TO-FP: Intrinsic has incorrect argument type!
+
+declare <4 x i8> @llvm.convert.to.arbitrary.fp.v4i8.v4ptr(<4 x ptr>, metadata, metadata, i1)
+
+define <4 x i8> @bad_vec_ptr_to_fp(<4 x ptr> %p) {
+ %r = call <4 x i8> @llvm.convert.to.arbitrary.fp.v4i8.v4ptr(
+ <4 x ptr> %p, metadata !"Float8E4M3", metadata !"round.tonearest", i1 false)
+ ret <4 x i8> %r
+}
+
+;--- vec-to-scalar-mismatch.ll
+; VEC-SCALAR-MISMATCH: if floating-point operand is a vector, integer operand must also be a vector
+
+declare i8 @llvm.convert.to.arbitrary.fp.i8.v4f16(<4 x half>, metadata, metadata, i1)
+
+define i8 @bad_vec_to_scalar(<4 x half> %v) {
+ %r = call i8 @llvm.convert.to.arbitrary.fp.i8.v4f16(
+ <4 x half> %v, metadata !"Float8E4M3", metadata !"round.tonearest", i1 false)
+ ret i8 %r
+}
+
+;--- vec-size-mismatch.ll
+; VEC-SIZE-MISMATCH: floating-point and integer vector operands must have the same element count
+
+declare <4 x i8> @llvm.convert.to.arbitrary.fp.v4i8.v2f32(<2 x float>, metadata, metadata, i1)
+
+define <4 x i8> @bad_vec_size_mismatch(<2 x float> %v) {
+ %r = call <4 x i8> @llvm.convert.to.arbitrary.fp.v4i8.v2f32(
+ <2 x float> %v, metadata !"Float8E4M3", metadata !"round.tonearest", i1 false)
+ ret <4 x i8> %r
+}
diff --git a/llvm/unittests/ADT/APFloatTest.cpp b/llvm/unittests/ADT/APFloatTest.cpp
index 99cc38b6b422b..8ff3efe64c29b 100644
--- a/llvm/unittests/ADT/APFloatTest.cpp
+++ b/llvm/unittests/ADT/APFloatTest.cpp
@@ -10182,4 +10182,28 @@ TEST(APFloatTest, FrexpQuietSNaN) {
EXPECT_FALSE(Result.isSignaling());
}
+TEST(APFloatTest, isValidArbitraryFPFormat) {
+ // Test all valid format strings.
+ EXPECT_TRUE(APFloat::isValidArbitraryFPFormat("Float8E5M2"));
+ EXPECT_TRUE(APFloat::isValidArbitraryFPFormat("Float8E5M2FNUZ"));
+ EXPECT_TRUE(APFloat::isValidArbitraryFPFormat("Float8E4M3"));
+ EXPECT_TRUE(APFloat::isValidArbitraryFPFormat("Float8E4M3FN"));
+ EXPECT_TRUE(APFloat::isValidArbitraryFPFormat("Float8E4M3FNUZ"));
+ EXPECT_TRUE(APFloat::isValidArbitraryFPFormat("Float8E4M3B11FNUZ"));
+ EXPECT_TRUE(APFloat::isValidArbitraryFPFormat("Float8E3M4"));
+ EXPECT_TRUE(APFloat::isValidArbitraryFPFormat("Float8E8M0FNU"));
+ EXPECT_TRUE(APFloat::isValidArbitraryFPFormat("Float6E3M2FN"));
+ EXPECT_TRUE(APFloat::isValidArbitraryFPFormat("Float6E2M3FN"));
+ EXPECT_TRUE(APFloat::isValidArbitraryFPFormat("Float4E2M1FN"));
+
+ // Test invalid format strings.
+ EXPECT_FALSE(APFloat::isValidArbitraryFPFormat(""));
+ EXPECT_FALSE(APFloat::isValidArbitraryFPFormat("Float8"));
+ EXPECT_FALSE(
+ APFloat::isValidArbitraryFPFormat("Float8E5M2FN")); // Should be FNUZ.
+ EXPECT_FALSE(APFloat::isValidArbitraryFPFormat("float8e4m3")); // Wrong case.
+ EXPECT_FALSE(APFloat::isValidArbitraryFPFormat("Float16E5M10"));
+ EXPECT_FALSE(APFloat::isValidArbitraryFPFormat("unknown"));
+}
+
} // namespace
More information about the llvm-commits
mailing list