diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index 033910121a54f..fd21ecffa0aed 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -21406,6 +21406,69 @@ environment ` *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 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Syntax: +""""""" + +:: + + declare @llvm.arbitrary.fp.convert( + , metadata , + metadata , metadata , + i32 ) + +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. + +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 + integer types), or any target-specific string for floating-point formats. + For example ``"spv.E4M3EXT"`` and ``"spv.E5M2EXT"`` stand for FP8 SPIR-V formats. + Using ``"none"`` indicates the converted bits already have the desired LLVM IR type. + +``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 ` (for example, + ``"round.tonearest"`` or ``"round.towardzero"``). The string ``"none"`` may be + used to indicate that the default rounding behaviour of the conversion should + be used. + +``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. + +Semantics: +"""""""""" + +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. + Convergence Intrinsics ---------------------- diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td index 12d1c2528f977..b0c8ea1e47fc7 100644 --- a/llvm/include/llvm/IR/Intrinsics.td +++ b/llvm/include/llvm/IR/Intrinsics.td @@ -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 ], + [ IntrNoMem, IntrSpeculatable ]>; + 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..58b80191625c5 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,52 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) { "unsupported rounding mode argument", Call); break; } + case Intrinsic::arbitrary_fp_convert: { + auto *ResultMAV = dyn_cast(Call.getArgOperand(1)); + Check(ResultMAV, "missing result interpretation metadata operand", Call); + auto *ResultStr = dyn_cast(ResultMAV->getMetadata()); + Check(ResultStr, "result interpretation metadata operand must be a string", + Call); + StringRef ResultInterp = ResultStr->getString(); + + auto *InputMAV = dyn_cast(Call.getArgOperand(2)); + Check(InputMAV, "missing input interpretation metadata operand", Call); + auto *InputStr = dyn_cast(InputMAV->getMetadata()); + Check(InputStr, "input interpretation metadata operand must be a string", + Call); + StringRef InputInterp = InputStr->getString(); + + auto *RoundingMAV = dyn_cast(Call.getArgOperand(3)); + 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); + StringRef RoundingInterp = RoundingStr->getString(); + + // 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 RM = + convertStrToRoundingMode(RoundingInterp); + Check(RM && *RM != RoundingMode::Dynamic, + "unsupported rounding mode argument", Call); + } + + // Check saturation parameter (must be 0 or 1) + auto *SaturationOp = dyn_cast(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 diff --git a/llvm/test/Verifier/arbitrary-fp-convert.ll b/llvm/test/Verifier/arbitrary-fp-convert.ll new file mode 100644 index 0000000000000..68cab2cb05dc1 --- /dev/null +++ b/llvm/test/Verifier/arbitrary-fp-convert.ll @@ -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 + +;--- bad-result.ll +; BADRESULT: result interpretation metadata string must not be empty +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 +}