Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21406,6 +21406,144 @@ 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 :ref:`llvm.fptrunc.round <int_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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could really use some examples of different type combinations.

As I understand it, there are basically three overloads here a) IR FP type, b) IR integer type, c) IR integer type interpreted as FP type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 again, I was thinking that some examples would be helpful.

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>,
metadata <rounding mode>, i1 <saturation>)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For fp-to-fp conversions, what does the "saturation" bit do? For formats that have inf/-inf, the "representable range" is infinite. Do some of the formats not have infinity? For such formats, is it really useful to allow non-saturating conversions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be "clamp" is a better name, though OCP also uses "saturate". Basically, if a format have representation of inf, we still should be able to specify, that rounding of out of range values clamps to max/min normal value instead of +/- inf. See https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf Table 3:

An implementation must support the saturate
(SAT) and overflow (OVF) methods outlined in Table 3 for handling conversions of values from
another format to FP8.

Note, that E5M2 format has infinity representation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the documentation to state more clearly what happens when "saturation" is false, for destination formats that have infinity, and formats that don't have infinity, since the spec has specific requirements.


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 <int_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
----------------------

Expand Down
16 changes: 16 additions & 0 deletions llvm/include/llvm/IR/Intrinsics.td
Original file line number Diff line number Diff line change
Expand Up @@ -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<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, llvm_metadata_ty, llvm_i1_ty ],
[ IntrNoMem, IntrSpeculatable, ImmArg<ArgIndex<3>> ]>;

def int_canonicalize : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], [LLVMMatchType<0>],
[IntrNoMem]>;
// Arithmetic fence intrinsic.
Expand Down
5 changes: 4 additions & 1 deletion llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2842,7 +2842,10 @@ bool IRTranslator::translateCall(const User &U, MachineIRBuilder &MIRBuilder) {
if (!MDN) {
if (auto *ConstMD = dyn_cast<ConstantAsMetadata>(MD))
MDN = MDNode::get(MF->getFunction().getContext(), ConstMD);
else // This was probably an MDString.
else if (auto *MDS = dyn_cast<MDString>(MD)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This came up in another review recently; why does this need to wrap metadata in MDNode?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather keep the codegen implementation separate from the patch which introduces the intrinsic definition, but that should include DAG glue too

Metadata *Ops[] = {MDS};
MDN = MDNode::get(MF->getFunction().getContext(), Ops);
Comment on lines +2846 to +2847
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Metadata *Ops[] = {MDS};
MDN = MDNode::get(MF->getFunction().getContext(), Ops);
MDN = MDNode::get(MF->getFunction().getContext(), {MDS});

} else
return false;
}
MIB.addMetadata(MDN);
Expand Down
34 changes: 34 additions & 0 deletions llvm/lib/IR/Verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Check interpretation metadata (argoperand 1)
// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Valid interpretation strings: mini-float format names
// 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";
Comment on lines +5865 to +5870
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in a more accessible utility somewhere, rather than buried in this one verifier function

Check(IsKnown, "unsupported interpretation metadata string", Call);

// Check rounding mode metadata (argoperand 2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Check rounding mode metadata (argoperand 2)
// 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);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to manually verify consistent element counts

std::optional<RoundingMode> 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
Expand Down
Loading
Loading