diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index 033910121a54f..b48177c9df5cd 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -21406,6 +21406,144 @@ 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 :ref:`llvm.fptrunc.round ` (for example, + ``"round.tonearest"`` or ``"round.towardzero"``). + +``saturation`` + A compile-time constant boolean value (``i1``). When ``true``, values outside the + representable range of the target format are clamped to the minimum or maximum normal value. + When ``false``, no saturation is applied. 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 result is returned as an integer (e.g., ``i8`` for FP8, +``i6`` for FP6) containing the encoded arbitrary FP bits. When saturation is enabled, +values that exceed the representable range are clamped to the minimum or maximum +normal value of the target format. + +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 , + metadata , i1 ) + +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"`` + +``rounding mode`` + A metadata string specifying the rounding mode. The permitted strings match those + accepted by :ref:`llvm.fptrunc.round ` (for example, + ``"round.tonearest"`` or ``"round.towardzero"``). + +``saturation`` + A compile-time constant boolean value (``i1``). When ``true``, values outside the + representable range of the target format are clamped to the minimum or maximum normal value. + When ``false``, no saturation is applied. This parameter must be an immediate constant. + +Semantics: +"""""""""" + +The intrinsic interprets the integer value as arbitrary FP bits according to +``interpretation``, then converts to the native LLVM floating-point result type, +applying the requested rounding mode and saturation behavior. When saturation is +enabled, values that exceed the representable range of the target format are +clamped to the minimum or maximum normal value. + +Example: +"""""""" + +:: + + ; Convert FP8 E4M3 bits to half + %half_val = call half @llvm.convert.from.arbitrary.fp.f16.i8( + i8 %fp8bits, metadata !"Float8E4M3", + metadata !"round.tonearest", i1 false) + + ; 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", + metadata !"round.tonearest", i1 false) + Convergence Intrinsics ---------------------- diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td index 12d1c2528f977..e8c9d02cb091b 100644 --- a/llvm/include/llvm/IR/Intrinsics.td +++ b/llvm/include/llvm/IR/Intrinsics.td @@ -1091,6 +1091,22 @@ let IntrProperties = [IntrNoMem, IntrSpeculatable] 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, llvm_metadata_ty, llvm_i1_ty ], + [ IntrNoMem, IntrSpeculatable, ImmArg> ]>; + def int_canonicalize : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], [LLVMMatchType<0>], [IntrNoMem]>; // Arithmetic fence intrinsic. diff --git a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp index 884c3f1692e94..f0a6c7082985e 100644 --- a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp +++ b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp @@ -2842,7 +2842,10 @@ bool IRTranslator::translateCall(const User &U, MachineIRBuilder &MIRBuilder) { if (!MDN) { if (auto *ConstMD = dyn_cast(MD)) MDN = MDNode::get(MF->getFunction().getContext(), ConstMD); - else // This was probably an MDString. + else if (auto *MDS = dyn_cast(MD)) { + Metadata *Ops[] = {MDS}; + MDN = MDNode::get(MF->getFunction().getContext(), Ops); + } else return false; } MIB.addMetadata(MDN); diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp index 03da1547b652f..fde68d1ad7aa5 100644 --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -80,6 +80,7 @@ #include "llvm/IR/Dominators.h" #include "llvm/IR/EHPersonalities.h" #include "llvm/IR/Function.h" +#include "llvm/IR/FPEnv.h" #include "llvm/IR/GCStrategy.h" #include "llvm/IR/GlobalAlias.h" #include "llvm/IR/GlobalValue.h" @@ -5848,6 +5849,39 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) { "unsupported rounding mode argument", Call); break; } + case Intrinsic::convert_to_arbitrary_fp: + case Intrinsic::convert_from_arbitrary_fp: { + // 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 + bool IsKnown = Interp == "Float8E5M2" || Interp == "Float8E5M2FNUZ" || + Interp == "Float8E4M3" || Interp == "Float8E4M3FN" || + Interp == "Float8E4M3FNUZ" || Interp == "Float8E4M3B11FNUZ" || + Interp == "Float8E3M4" || Interp == "Float8E8M0FNU" || + Interp == "Float6E3M2FN" || Interp == "Float6E2M3FN" || + Interp == "Float4E2M1FN"; + Check(IsKnown, "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; + } #define BEGIN_REGISTER_VP_INTRINSIC(VPID, ...) case Intrinsic::VPID: #include "llvm/IR/VPIntrinsics.def" #undef BEGIN_REGISTER_VP_INTRINSIC diff --git a/llvm/test/Verifier/arbitrary-fp-convert.ll b/llvm/test/Verifier/arbitrary-fp-convert.ll new file mode 100644 index 0000000000000..30202e6ade6fe --- /dev/null +++ b/llvm/test/Verifier/arbitrary-fp-convert.ll @@ -0,0 +1,187 @@ +; RUN: split-file %s %t +; RUN: not llvm-as %t/bad-interpretation-empty.ll -o /dev/null 2>&1 | FileCheck %s --check-prefix=BAD-INTERP-EMPTY +; RUN: not llvm-as %t/bad-interpretation-unknown.ll -o /dev/null 2>&1 | FileCheck %s --check-prefix=BAD-INTERP-UNKNOWN +; RUN: not llvm-as %t/bad-rounding.ll -o /dev/null 2>&1 | FileCheck %s --check-prefix=BAD-ROUNDING +; RUN: llvm-as %t/good.ll -o /dev/null + +;--- 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 +} + +;--- good.ll +; Test valid scalar and vector conversions (FP to FP only) + +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, metadata, i1) +declare <4 x half> @llvm.convert.from.arbitrary.fp.v4f16.v4i8(<4 x i8>, metadata, metadata, i1) +declare <8 x half> @llvm.convert.from.arbitrary.fp.v8f16.v8i8(<8 x i8>, metadata, metadata, i1) +declare float @llvm.convert.from.arbitrary.fp.f32.i8(i8, metadata, metadata, i1) +declare <4 x float> @llvm.convert.from.arbitrary.fp.v4f32.v4i8(<4 x i8>, metadata, metadata, i1) + +; Scalar conversions to arbitrary FP +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 +} + +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 +} + +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 +define half @good_fp8_to_half(i8 %v) { + %r = call half @llvm.convert.from.arbitrary.fp.f16.i8( + i8 %v, metadata !"Float8E4M3", metadata !"round.tonearest", i1 false) + ret half %r +} + +define half @good_fp8_e5m2_to_half(i8 %v) { + %r = call half @llvm.convert.from.arbitrary.fp.f16.i8( + i8 %v, metadata !"Float8E5M2", metadata !"round.tonearest", i1 false) + ret half %r +} + +define float @good_fp8_to_float(i8 %v) { + %r = call float @llvm.convert.from.arbitrary.fp.f32.i8( + i8 %v, metadata !"Float8E4M3", metadata !"round.tonearest", i1 false) + ret float %r +} + +; Vector conversions to arbitrary FP +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 +} + +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 +} + +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 +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", metadata !"round.tonearest", i1 false) + ret <4 x half> %r +} + +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", metadata !"round.tonearest", i1 false) + ret <8 x half> %r +} + +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", metadata !"round.tonearest", i1 false) + ret <4 x float> %r +} + +; Test different rounding modes +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 +} + +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 +} + +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 +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 +} + +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 +} + +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 +} + +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 +} + +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 +} + +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)