diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index be752b42ea412..4d34a12ecc1cf 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -21714,6 +21714,164 @@ environment ` *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 @llvm.convert.to.arbitrary.fp..( + , metadata , + metadata , i1 ) + +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 `. 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 @llvm.convert.from.arbitrary.fp..( + , metadata ) + +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 +`; 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 b2d5ea33f888c..f76014730d589 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> ]>; + + // 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(ValueTy)) { + auto *IntVecTy = dyn_cast(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(Call.getArgOperand(1)); + Check(InterpMAV, "missing interpretation metadata operand", Call); + auto *InterpStr = dyn_cast(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(Call.getArgOperand(2)); + Check(RoundingMAV, "missing rounding mode metadata operand", Call); + auto *RoundingStr = dyn_cast(RoundingMAV->getMetadata()); + Check(RoundingStr, "rounding mode metadata operand must be a string", Call); + + std::optional 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(ValueTy)) { + auto *IntVecTy = dyn_cast(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(Call.getArgOperand(1)); + Check(InterpMAV, "missing interpretation metadata operand", Call); + auto *InterpStr = dyn_cast(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 e2645fa46bbcd..85b097b74d09b 100644 --- a/llvm/lib/Support/APFloat.cpp +++ b/llvm/lib/Support/APFloat.cpp @@ -6155,6 +6155,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(*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 different 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