Skip to content
63 changes: 63 additions & 0 deletions llvm/docs/LangRef.rst

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd add a note to both intrinsics that the supported conversions are target dependent. (These aren't going to get generic legalization support, right?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a note

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These should get generic legalization support

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this note just makes things worse. It's stating poor QoI as a goal.

Many intrinsics are broken for different type combinations on different targets, but this isn't a desirable state. There isn't anything target dependent required to legalize these

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My expectation for these intrinsics is that they indeed do not have generic legalization support. They're just a generic spelling for target-specific conversions.

These intrinsics, if they were legalized, should use libcall legalizations, but there are no libcalls for these and I don't expect that they are going to be introduced, so I don't think legalization support makes a lot of sense.

Having someone implement inline expansions for all the type combinations without actually having a use case for it sounds like a massive waste of time.

@MrSidims MrSidims Jan 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

What I had in mind is that for FP4 conversions legalization is quite trivial in case if add a look-up table, then FP4 value becomes just an index. After double checking - FP6 case seem to be also trivial as it's indeed just bit shuffling + rounding.

There are FP8 + FN/FNUZ/E8M0FNU case, where generic lowering stops being “just shuffle + one rounding bit” and becomes “shuffle + full special-case semantics + careful NaN/zero rules.”. I lean towards agreeing, that generic legalization is feasible, yet not 100% sure if there will be interest for the intrinsics outside of the use cases, when a hardware supports the conversions in a performant way.

@MrSidims MrSidims Jan 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Do you want me to remove the note and implement generic legalization in this PR? I'm asking as I'm not 100% sure if I'll be able to work on this very PR past January 14th due to a job switch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed the note. Lets actually land some implementation and then see how it goes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Leaving the legalization out of this PR is fine for now, but I do think it should be implemented. That significantly increases the utility of the intrinsics.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

e.g., for single source languages it's very useful to have common operations you can rely on for the host and device code. Restrictions to device-only are limiting

Original file line number Diff line number Diff line change
Expand Up @@ -21406,6 +21406,69 @@ environment <floatenv>` *except* for the rounding mode.
This intrinsic is not supported on all targets. Some targets may not support
all rounding modes.

'``llvm.arbitrary.fp.convert``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Comment thread
MrSidims marked this conversation as resolved.
Outdated

Syntax:
"""""""

::

declare <type> @llvm.arbitrary.fp.convert(
Comment thread
MrSidims marked this conversation as resolved.
Outdated
Comment thread
MrSidims marked this conversation as resolved.
Outdated
<type> <value>, metadata <result interpretation>,
metadata <input interpretation>, metadata <rounding mode>,
i32 <saturation>)

Overview:
"""""""""

The ``llvm.arbitrary.fp.convert`` intrinsic performs conversions
between values whose interpretation differs from their representation
in LLVM IR. The intrinsic is overloaded on both its return type and first
argument. Metadata operands describe how the raw bits should be interpreted
before and after the conversion.
Comment thread
MrSidims marked this conversation as resolved.
Outdated

Arguments:
""""""""""

``value``
The value to convert. Its interpretation is described by ``input
interpretation``.

``result interpretation``
A metadata string that describes the type of the result. The string
can be ``"none"`` (no conversion needed), ``"signed"`` or ``"unsigned"`` (for
Comment thread
MrSidims marked this conversation as resolved.
Outdated
integer types), or any target-specific string for floating-point formats.
For example ``"spv.E4M3EXT"`` and ``"spv.E5M2EXT"`` stand for FP8 SPIR-V formats.
Comment thread
MrSidims marked this conversation as resolved.
Using ``"none"`` indicates the converted bits already have the desired LLVM IR type.
Comment thread
MrSidims marked this conversation as resolved.
Outdated

``input interpretation``
Mirrors ``result interpretation`` but applies to the first argument. The
interpretation is target-specific and describes how to interpret the raw bits
of the input value.

``rounding mode``
A metadata string. The permitted strings match those accepted by
:ref:`llvm.fptrunc.round <int_fptrunc_round>` (for example,
Comment thread
MrSidims marked this conversation as resolved.
Outdated
``"round.tonearest"`` or ``"round.towardzero"``). The string ``"none"`` may be
used to indicate that the default rounding behaviour of the conversion should
be used.
Comment thread
MrSidims marked this conversation as resolved.
Outdated

``saturation``
An integer constant (0 or 1) indicating whether saturation should be applied
to the conversion. When set to 1, values outside the representable range of
the result type are clamped to the minimum or maximum representable value
instead of wrapping. When set to 0, no saturation is applied.
Comment thread
MrSidims marked this conversation as resolved.
Outdated

Semantics:
Comment thread
Keenuts marked this conversation as resolved.
""""""""""

The intrinsic interprets the first argument according to ``input
interpretation``, applies the requested rounding mode and saturation behavior,
and produces a value whose type is described by ``result interpretation``.
When saturation is enabled, values that exceed the representable range of the target
format are clamped to the minimum or maximum representable value of that format.

Comment thread
MrSidims marked this conversation as resolved.
Convergence Intrinsics
----------------------

Expand Down
8 changes: 8 additions & 0 deletions llvm/include/llvm/IR/Intrinsics.td
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,14 @@ let IntrProperties = [IntrNoMem, IntrSpeculatable] in {
def int_fptrunc_round : DefaultAttrsIntrinsic<[ llvm_anyfloat_ty ],
[ llvm_anyfloat_ty, llvm_metadata_ty ]>;

// Convert between arbitrary interpreted floating-point and integer values.
def int_arbitrary_fp_convert
: DefaultAttrsIntrinsic<
[ llvm_any_ty ],
[ llvm_any_ty, llvm_metadata_ty, llvm_metadata_ty,
llvm_metadata_ty, llvm_i32_ty ],
Comment thread
MrSidims marked this conversation as resolved.
Outdated
Comment thread
MrSidims marked this conversation as resolved.
Outdated
[ IntrNoMem, IntrSpeculatable ]>;

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)) {
Comment thread
MrSidims marked this conversation as resolved.
Outdated
Comment thread
MrSidims marked this conversation as resolved.
Outdated
Metadata *Ops[] = {MDS};
MDN = MDNode::get(MF->getFunction().getContext(), Ops);
Comment thread
MrSidims marked this conversation as resolved.
Outdated
} else
return false;
}
MIB.addMetadata(MDN);
Expand Down
47 changes: 47 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,52 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) {
"unsupported rounding mode argument", Call);
break;
}
case Intrinsic::arbitrary_fp_convert: {
auto *ResultMAV = dyn_cast<MetadataAsValue>(Call.getArgOperand(1));
Check(ResultMAV, "missing result interpretation metadata operand", Call);
auto *ResultStr = dyn_cast<MDString>(ResultMAV->getMetadata());
Check(ResultStr, "result interpretation metadata operand must be a string",
Call);
StringRef ResultInterp = ResultStr->getString();

auto *InputMAV = dyn_cast<MetadataAsValue>(Call.getArgOperand(2));
Check(InputMAV, "missing input interpretation metadata operand", Call);
auto *InputStr = dyn_cast<MDString>(InputMAV->getMetadata());
Check(InputStr, "input interpretation metadata operand must be a string",
Call);
StringRef InputInterp = InputStr->getString();

auto *RoundingMAV = dyn_cast<MetadataAsValue>(Call.getArgOperand(3));
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);
StringRef RoundingInterp = RoundingStr->getString();
Comment thread
MrSidims marked this conversation as resolved.
Outdated

// Check that interpretation strings are not empty. The actual interpretation
// values are target-specific and not validated here.
Check(!ResultInterp.empty(),
"result interpretation metadata string must not be empty", Call);
Check(!InputInterp.empty(),
"input interpretation metadata string must not be empty", Call);

if (RoundingInterp != "none") {
std::optional<RoundingMode> RM =
convertStrToRoundingMode(RoundingInterp);
Check(RM && *RM != RoundingMode::Dynamic,
"unsupported rounding mode argument", Call);
}

Comment thread
MrSidims marked this conversation as resolved.
// Check saturation parameter (must be 0 or 1)
auto *SaturationOp = dyn_cast<ConstantInt>(Call.getArgOperand(4));
Check(SaturationOp, "saturation operand must be a constant integer", Call);
if (SaturationOp) {
uint64_t SatVal = SaturationOp->getZExtValue();
Check(SatVal == 0 || SatVal == 1,
"saturation operand must be 0 or 1", Call);
}
break;
}
#define BEGIN_REGISTER_VP_INTRINSIC(VPID, ...) case Intrinsic::VPID:
#include "llvm/IR/VPIntrinsics.def"
#undef BEGIN_REGISTER_VP_INTRINSIC
Expand Down
66 changes: 66 additions & 0 deletions llvm/test/Verifier/arbitrary-fp-convert.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
; RUN: split-file %s %t
; RUN: not opt -S -passes=verify %t/bad-result.ll 2>&1 | FileCheck %s --check-prefix=BADRESULT
; RUN: not opt -S -passes=verify %t/bad-rounding.ll 2>&1 | FileCheck %s --check-prefix=BADROUND
; RUN: not opt -S -passes=verify %t/bad-saturation.ll 2>&1 | FileCheck %s --check-prefix=BADSAT
; RUN: opt -S -passes=verify %t/good.ll
Comment thread
MrSidims marked this conversation as resolved.
Outdated

;--- bad-result.ll
; BADRESULT: result interpretation metadata string must not be empty
Comment thread
MrSidims marked this conversation as resolved.
Outdated
declare half @llvm.arbitrary.fp.convert.half.i8(i8, metadata, metadata, metadata, i32)

define half @bad_result(i8 %v) {
%r = call half @llvm.arbitrary.fp.convert.half.i8(
i8 %v, metadata !"", metadata !"spv.E5M2EXT", metadata !"none", i32 0)
ret half %r
}

;--- bad-rounding.ll
; BADROUND: unsupported rounding mode argument
declare i8 @llvm.arbitrary.fp.convert.i8.half(half, metadata, metadata, metadata, i32)

define i8 @bad_rounding(half %v) {
%r = call i8 @llvm.arbitrary.fp.convert.i8.half(
half %v, metadata !"spv.E4M3EXT", metadata !"none", metadata !"round.dynamic", i32 0)
ret i8 %r
}

;--- bad-saturation.ll
; BADSAT: saturation operand must be 0 or 1
declare i8 @llvm.arbitrary.fp.convert.i8.half.sat(half, metadata, metadata, metadata, i32)

define i8 @bad_saturation(half %v) {
%r = call i8 @llvm.arbitrary.fp.convert.i8.half.sat(
half %v, metadata !"spv.E4M3EXT", metadata !"none", metadata !"round.towardzero", i32 2)
ret i8 %r
}

;--- good.ll
declare half @llvm.arbitrary.fp.convert.half.i8(i8, metadata, metadata, metadata, i32)
declare i8 @llvm.arbitrary.fp.convert.i8.half(half, metadata, metadata, metadata, i32)
declare i32 @llvm.arbitrary.fp.convert.i32.i8(i8, metadata, metadata, metadata, i32)
declare i8 @llvm.arbitrary.fp.convert.i8.i32(i32, metadata, metadata, metadata, i32)

define half @good_from(i8 %v) {
%r = call half @llvm.arbitrary.fp.convert.half.i8(
i8 %v, metadata !"none", metadata !"spv.E4M3EXT", metadata !"none", i32 0)
ret half %r
}

define i8 @good_to(half %v) {
%r = call i8 @llvm.arbitrary.fp.convert.i8.half(
half %v, metadata !"spv.E4M3EXT", metadata !"none", metadata !"round.towardzero", i32 0)
ret i8 %r
}

; Test integer conversions with rounding modes - these are now allowed
define i32 @good_int_rounding(i8 %v) {
%r = call i32 @llvm.arbitrary.fp.convert.i32.i8(
i8 %v, metadata !"signed", metadata !"spv.E4M3EXT", metadata !"none", i32 0)
ret i32 %r
}

define i8 @good_input_rounding(i32 %v) {
%r = call i8 @llvm.arbitrary.fp.convert.i8.i32(
i32 %v, metadata !"spv.E4M3EXT", metadata !"signed", metadata !"none", i32 0)
ret i8 %r
}
Comment thread
MrSidims marked this conversation as resolved.
Loading