[Mlir-commits] [mlir] [MLIR][LLVMIR] Add an operation to handle constrained FP intrinsics (PR #199754)
Andy Kaylor
llvmlistbot at llvm.org
Tue May 26 12:52:07 PDT 2026
https://github.com/andykaylor created https://github.com/llvm/llvm-project/pull/199754
The LLVM dialect currently only handles a subset of all the constrained FP intrinsics defined in LLVM IR. Attempts to import LLVM IR that uses any of the others results in an assertion failure while trying to parse the metadata arguments.
This change introduces a single operation, CallConstrainedFPIntrinsicOp, that handles most of the others in a way that's analagous to the general CallIntrinsicOp but with special handling for the metadata arguments. This doesn't handle the constrained fcmp/fcmps intrinsics because they need additional argument handling. I'll add them in a follow-up change.
I'm introducing this now because I'd like to add strict FP handling to CIR, but I think this should be viewed as a temporary solution because (hopefully) the constrained intrinsics in LLVM IR will be replaced soon(ish). That is the primary reason I chose not to have individual operations for each of these. I hope we'll be able to replace this with a better representation soon.
Assisted-by: Cursor / claude-opus-4.7-thinking-xhigh
>From 0004f352b0d5071280a131ab5da1b6519254b0d8 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Fri, 22 May 2026 16:52:48 -0700
Subject: [PATCH] [MLIR][LLVMIR] Add an operation to handle constrained FP
intrinsics
The LLVM dialect currently only handles a subset of all the constrained
FP intrinsics defined in LLVM IR. Attempts to import LLVM IR that uses
any of the others results in an assertion failure while trying to parse
the metadata arguments.
This change introduces a single operation, CallConstrainedFPIntrinsicOp,
that handles most of the others in a way that's analagous to the
general CallIntrinsicOp but with special handling for the metadata
arguments. This doesn't handle the constrained fcmp/fcmps intrinsics
because they need additional argument handling. I'll add them in a
follow-up change.
I'm introducing this now because I'd like to add strict FP handling
to CIR, but I think this should be viewed as a temporary solution
because (hopefully) the constrained intrinsics in LLVM IR will be
replaced soon(ish). That is the primary reason I chose not to have
individual operations for each of these. I hope we'll be able to
replace this with a better representation soon.
Assisted-by: Cursor / claude-opus-4.7-thinking-xhigh
---
.../mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td | 51 ++++++++
mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp | 29 +++++
.../LLVMIR/LLVMIRToLLVMTranslation.cpp | 116 +++++++++++++++++-
.../LLVMIR/LLVMToLLVMIRTranslation.cpp | 61 +++++++++
mlir/test/Dialect/LLVMIR/invalid.mlir | 40 ++++++
mlir/test/Dialect/LLVMIR/roundtrip.mlir | 17 +++
.../Target/LLVMIR/Import/import-failure.ll | 20 +++
mlir/test/Target/LLVMIR/Import/intrinsic.ll | 25 ++++
.../test/Target/LLVMIR/llvmir-intrinsics.mlir | 35 ++++++
9 files changed, 390 insertions(+), 4 deletions(-)
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
index 688bc19cbf18a..0785ff6ebfd49 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
@@ -596,6 +596,57 @@ def LLVM_ConstrainedFPExtIntr
}];
}
+// Generic constrained floating-point intrinsic call.
+
+def LLVM_CallConstrainedFPIntrinsicOp
+ : LLVM_Op<"intr.experimental.constrained_fp_call",
+ [Pure,
+ DeclareOpInterfaceMethods<FPExceptionBehaviorOpInterface>,
+ DeclareOpInterfaceMethods<RoundingModeOpInterface>]> {
+ let summary = "Generic call to an LLVM constrained floating-point intrinsic.";
+ let description = [{
+ Calls an LLVM constrained floating-point intrinsic by name. The intrinsic
+ name is given by the `intrin` attribute (for example
+ `"llvm.experimental.constrained.cos.f32"`). Overloaded intrinsics are
+ resolved from the MLIR operand and result types of this op.
+
+ The rounding mode operand is required for intrinsics for which
+ `llvm::Intrinsic::hasConstrainedFPRoundingModeOperand` returns true and is
+ forbidden otherwise. The exception behavior attribute is always required.
+
+ This op handles every constrained FP intrinsic that follows the standard
+ operand layout `(args..., [rounding,] exception)`. The compare variants
+ `llvm.experimental.constrained.fcmp` and
+ `llvm.experimental.constrained.fcmps` carry an additional predicate
+ metadata operand and are not supported.
+
+ Example:
+
+ ```mlir
+ %res = llvm.intr.experimental.constrained_fp_call
+ "llvm.experimental.constrained.cos.f32"(%arg)
+ towardzero ignore : (f32) -> f32
+ ```
+ }];
+
+ let arguments = (ins StrAttr:$intrin,
+ Variadic<LLVM_Type>:$args,
+ OptionalAttr<ValidRoundingModeAttr>:$roundingmode,
+ FPExceptionBehaviorAttr:$fpExceptionBehavior);
+ let results = (outs LLVM_Type:$res);
+
+ let llvmBuilder = [{
+ return convertCallConstrainedFPIntrinsicOp(op, builder, moduleTranslation);
+ }];
+
+ let assemblyFormat = [{
+ $intrin `(` $args `)` ($roundingmode^)? $fpExceptionBehavior
+ attr-dict `:` functional-type($args, $res)
+ }];
+
+ let hasVerifier = 1;
+}
+
// Intrinsics with multiple returns.
class LLVM_ArithWithOverflowOp<string mnem>
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index 63bd9f8a3d625..5ee8f5004cf48 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -27,6 +27,7 @@
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/Intrinsics.h"
#include "llvm/Support/Error.h"
#include "LLVMDialectBytecode.h"
@@ -4012,6 +4013,34 @@ LogicalResult CallIntrinsicOp::verify() {
return success();
}
+//===----------------------------------------------------------------------===//
+// CallConstrainedFPIntrinsicOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult CallConstrainedFPIntrinsicOp::verify() {
+ StringRef name = getIntrin();
+ llvm::Intrinsic::ID id = llvm::Intrinsic::lookupIntrinsicID(name);
+ if (!id)
+ return emitOpError() << "could not find LLVM intrinsic: " << name;
+ if (!llvm::Intrinsic::isConstrainedFPIntrinsic(id))
+ return emitOpError() << "intrinsic " << name
+ << " is not a constrained FP intrinsic";
+ if (id == llvm::Intrinsic::experimental_constrained_fcmp ||
+ id == llvm::Intrinsic::experimental_constrained_fcmps)
+ return emitOpError() << "intrinsic " << name
+ << " is a constrained FP compare and is not "
+ "supported by this op";
+ bool requiresRounding =
+ llvm::Intrinsic::hasConstrainedFPRoundingModeOperand(id);
+ if (requiresRounding && !getRoundingmodeAttr())
+ return emitOpError() << "intrinsic " << name
+ << " requires a rounding mode attribute";
+ if (!requiresRounding && getRoundingmodeAttr())
+ return emitOpError() << "intrinsic " << name
+ << " does not take a rounding mode attribute";
+ return success();
+}
+
void CallIntrinsicOp::build(OpBuilder &builder, OperationState &state,
mlir::StringAttr intrin, mlir::ValueRange args) {
build(builder, state, /*resultTypes=*/TypeRange{}, intrin, args,
diff --git a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp
index e9cd335835263..89105242e7180 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp
@@ -22,6 +22,7 @@
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/MemoryModelRelaxationAnnotations.h"
using namespace mlir;
@@ -36,24 +37,113 @@ static constexpr StringLiteral reqdWorkGroupSizeMDName = "reqd_work_group_size";
static constexpr StringLiteral intelReqdSubGroupSizeMDName =
"intel_reqd_sub_group_size";
+/// Returns true if `id` is a constrained FP intrinsic that the generic
+/// LLVM_CallConstrainedFPIntrinsicOp can model (i.e. it has the standard
+/// trailing metadata layout: rounding mode and/or exception behavior, with no
+/// additional predicate metadata).
+static bool isGenericConstrainedFPIntrinsic(llvm::Intrinsic::ID id) {
+ if (!llvm::Intrinsic::isConstrainedFPIntrinsic(id))
+ return false;
+ // fcmp / fcmps carry an extra predicate metadata operand and are not
+ // representable by the generic op.
+ return id != llvm::Intrinsic::experimental_constrained_fcmp &&
+ id != llvm::Intrinsic::experimental_constrained_fcmps;
+}
+
+/// Returns true if `id` is a constrained FP compare intrinsic. These have a
+/// predicate metadata operand in addition to the exception behavior operand
+/// and are not currently importable, but should fail with a clean diagnostic
+/// instead of falling through to the generic intrinsic path and tripping the
+/// metadata assertion in `convertValue`.
+static bool isConstrainedFPCmpIntrinsic(llvm::Intrinsic::ID id) {
+ return id == llvm::Intrinsic::experimental_constrained_fcmp ||
+ id == llvm::Intrinsic::experimental_constrained_fcmps;
+}
+
/// Returns true if the LLVM IR intrinsic is convertible to an MLIR LLVM dialect
-/// intrinsic. Returns false otherwise.
+/// intrinsic. Returns false otherwise. Constrained FP compare intrinsics are
+/// claimed here so that the import emits a targeted error rather than crashing
+/// in the unregistered-intrinsic fallback.
static bool isConvertibleIntrinsic(llvm::Intrinsic::ID id) {
static const DenseSet<unsigned> convertibleIntrinsics = {
#include "mlir/Dialect/LLVMIR/LLVMConvertibleLLVMIRIntrinsics.inc"
};
- return convertibleIntrinsics.contains(id);
+ if (convertibleIntrinsics.contains(id))
+ return true;
+ return isGenericConstrainedFPIntrinsic(id) || isConstrainedFPCmpIntrinsic(id);
}
/// Returns the list of LLVM IR intrinsic identifiers that are convertible to
/// MLIR LLVM dialect intrinsics.
static ArrayRef<unsigned> getSupportedIntrinsicsImpl() {
- static const SmallVector<unsigned> convertibleIntrinsics = {
+ static const SmallVector<unsigned> convertibleIntrinsics = [] {
+ SmallVector<unsigned> ids = {
#include "mlir/Dialect/LLVMIR/LLVMConvertibleLLVMIRIntrinsics.inc"
- };
+ };
+ // Also register the constrained FP intrinsics that fall back to the
+ // generic LLVM_CallConstrainedFPIntrinsicOp. Compare variants are
+ // registered too so the importer can emit a clean error for them instead
+ // of letting them fall through to the unregistered-intrinsic path, which
+ // would trip the metadata assertion in `convertValue`.
+ DenseSet<unsigned> seen(ids.begin(), ids.end());
+ for (unsigned id = 1; id < llvm::Intrinsic::num_intrinsics; ++id) {
+ auto intrinId = static_cast<llvm::Intrinsic::ID>(id);
+ if (seen.contains(id))
+ continue;
+ if (isGenericConstrainedFPIntrinsic(intrinId) ||
+ isConstrainedFPCmpIntrinsic(intrinId))
+ ids.push_back(id);
+ }
+ return ids;
+ }();
return convertibleIntrinsics;
}
+/// Imports a constrained FP intrinsic call as a generic
+/// LLVM_CallConstrainedFPIntrinsicOp. Splits the call's operands into value
+/// arguments and the trailing rounding-mode/exception-behavior metadata
+/// operands.
+static LogicalResult
+convertConstrainedFPIntrinsicCallOp(OpBuilder &builder, llvm::CallInst *inst,
+ LLVM::ModuleImport &moduleImport) {
+ llvm::Intrinsic::ID id = inst->getIntrinsicID();
+ llvm::Function *callee = inst->getCalledFunction();
+ if (!callee)
+ return failure();
+ StringRef intrinName = callee->getName();
+ bool hasRounding = llvm::Intrinsic::hasConstrainedFPRoundingModeOperand(id);
+
+ unsigned numArgs = inst->arg_size();
+ unsigned numMetadata = hasRounding ? 2 : 1;
+ if (numArgs < numMetadata)
+ return failure();
+ unsigned numValueArgs = numArgs - numMetadata;
+
+ SmallVector<Value> args;
+ args.reserve(numValueArgs);
+ for (unsigned i = 0; i < numValueArgs; ++i) {
+ FailureOr<Value> v = moduleImport.convertValue(inst->getArgOperand(i));
+ if (failed(v))
+ return failure();
+ args.push_back(*v);
+ }
+
+ RoundingModeAttr roundingMode;
+ if (hasRounding)
+ roundingMode =
+ moduleImport.matchRoundingModeAttr(inst->getArgOperand(numValueArgs));
+ FPExceptionBehaviorAttr exceptionBehavior =
+ moduleImport.matchFPExceptionBehaviorAttr(
+ inst->getArgOperand(numArgs - 1));
+
+ Type resultType = moduleImport.convertType(inst->getType());
+ auto op = CallConstrainedFPIntrinsicOp::create(
+ builder, moduleImport.translateLoc(inst->getDebugLoc()), resultType,
+ builder.getStringAttr(intrinName), args, roundingMode, exceptionBehavior);
+ moduleImport.mapValue(inst) = op.getRes();
+ return success();
+}
+
/// Converts the LLVM intrinsic to an MLIR LLVM dialect operation if a
/// conversion exits. Returns failure otherwise.
static LogicalResult convertIntrinsicImpl(OpBuilder &odsBuilder,
@@ -73,6 +163,24 @@ static LogicalResult convertIntrinsicImpl(OpBuilder &odsBuilder,
llvmOpBundles.push_back(inst->getOperandBundleAt(i));
#include "mlir/Dialect/LLVMIR/LLVMIntrinsicFromLLVMIRConversions.inc"
+
+ // Fallback for constrained FP intrinsics without a dedicated MLIR op.
+ if (isGenericConstrainedFPIntrinsic(intrinsicID))
+ return convertConstrainedFPIntrinsicCallOp(odsBuilder, inst,
+ moduleImport);
+
+ // Constrained FP compare intrinsics are claimed here so that we can emit
+ // a targeted error instead of falling through to convertUnregistered-
+ // Intrinsic (which would crash on the predicate metadata operand).
+ if (isConstrainedFPCmpIntrinsic(intrinsicID)) {
+ Location loc = moduleImport.translateLoc(inst->getDebugLoc());
+ StringRef intrinName = inst->getCalledFunction()
+ ? inst->getCalledFunction()->getName()
+ : StringRef("<unknown>");
+ return emitError(loc)
+ << "constrained FP compare intrinsic '" << intrinName
+ << "' is not supported by the LLVM dialect importer";
+ }
}
return failure();
diff --git a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
index 5474689c9b0b5..65cccbd055286 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
@@ -135,6 +135,67 @@ convertOperandBundles(OperandRangeRange bundleOperands,
return convertOperandBundles(bundleOperands, *bundleTags, moduleTranslation);
}
+/// Builder for LLVM_CallConstrainedFPIntrinsicOp. Resolves the intrinsic
+/// identifier from the `intrin` attribute, infers any overloaded types from the
+/// MLIR operand and result types, and emits an LLVM IR constrained FP call.
+static LogicalResult convertCallConstrainedFPIntrinsicOp(
+ CallConstrainedFPIntrinsicOp op, llvm::IRBuilderBase &builder,
+ LLVM::ModuleTranslation &moduleTranslation) {
+ llvm::Module *module = builder.GetInsertBlock()->getModule();
+ llvm::Intrinsic::ID id = llvm::Intrinsic::lookupIntrinsicID(op.getIntrin());
+ if (!id)
+ return mlir::emitError(op.getLoc(), "could not find LLVM intrinsic: ")
+ << op.getIntrin();
+ if (!llvm::Intrinsic::isConstrainedFPIntrinsic(id))
+ return mlir::emitError(op.getLoc(), "not a constrained FP intrinsic: ")
+ << op.getIntrin();
+ if (id == llvm::Intrinsic::experimental_constrained_fcmp ||
+ id == llvm::Intrinsic::experimental_constrained_fcmps)
+ return mlir::emitError(op.getLoc())
+ << op.getIntrin()
+ << " is a constrained FP compare and is not supported by this op";
+
+ // Build a signature matching what the intrinsic declaration looks like in
+ // LLVM IR, including the trailing metadata operands. This lets
+ // Intrinsic::isSignatureValid resolve all overloaded types.
+ SmallVector<llvm::Type *> argTys;
+ argTys.reserve(op.getArgs().size() + 2);
+ for (Type type : op.getArgs().getTypes())
+ argTys.push_back(moduleTranslation.convertType(type));
+ llvm::Type *metadataTy = llvm::Type::getMetadataTy(module->getContext());
+ if (llvm::Intrinsic::hasConstrainedFPRoundingModeOperand(id))
+ argTys.push_back(metadataTy);
+ argTys.push_back(metadataTy);
+
+ llvm::Type *resultTy = moduleTranslation.convertType(op.getRes().getType());
+ llvm::FunctionType *ft =
+ llvm::FunctionType::get(resultTy, argTys, /*isVarArg=*/false);
+
+ std::string errorMsg;
+ llvm::raw_string_ostream errorOS(errorMsg);
+ SmallVector<llvm::Type *> overloadedTys;
+ if (!llvm::Intrinsic::isSignatureValid(id, ft, overloadedTys, errorOS)) {
+ return mlir::emitError(op.getLoc(), "call intrinsic signature ")
+ << diagStr(ft) << " to constrained FP intrinsic " << op.getIntrin()
+ << " does not match any overload: " << errorMsg;
+ }
+
+ llvm::Function *fn =
+ llvm::Intrinsic::getOrInsertDeclaration(module, id, overloadedTys);
+
+ std::optional<llvm::RoundingMode> rounding;
+ if (auto roundingAttr = op.getRoundingmodeAttr())
+ rounding = moduleTranslation.translateRoundingMode(roundingAttr.getValue());
+ llvm::fp::ExceptionBehavior except =
+ moduleTranslation.translateFPExceptionBehavior(
+ op.getFpExceptionBehavior());
+
+ llvm::Value *result = builder.CreateConstrainedFPCall(
+ fn, moduleTranslation.lookupValues(op.getArgs()), "", rounding, except);
+ moduleTranslation.mapValue(op.getRes()) = result;
+ return success();
+}
+
/// Builder for LLVM_CallIntrinsicOp
static LogicalResult
convertCallLLVMIntrinsicOp(CallIntrinsicOp op, llvm::IRBuilderBase &builder,
diff --git a/mlir/test/Dialect/LLVMIR/invalid.mlir b/mlir/test/Dialect/LLVMIR/invalid.mlir
index e80094df1eed2..78f3736d13674 100644
--- a/mlir/test/Dialect/LLVMIR/invalid.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid.mlir
@@ -1783,6 +1783,46 @@ llvm.func @wrong_number_of_bundle_types_intrin(%arg0: i32) -> i32 {
// -----
+llvm.func @constrained_fp_call_unknown_intrinsic(%arg0: f32) -> f32 {
+ // expected-error at +1 {{could not find LLVM intrinsic: llvm.experimental.constrained.bogus.f32}}
+ %0 = llvm.intr.experimental.constrained_fp_call "llvm.experimental.constrained.bogus.f32"(%arg0) towardzero ignore : (f32) -> f32
+ llvm.return %0 : f32
+}
+
+// -----
+
+llvm.func @constrained_fp_call_not_constrained(%arg0: f32) -> f32 {
+ // expected-error at +1 {{intrinsic llvm.cos.f32 is not a constrained FP intrinsic}}
+ %0 = llvm.intr.experimental.constrained_fp_call "llvm.cos.f32"(%arg0) towardzero ignore : (f32) -> f32
+ llvm.return %0 : f32
+}
+
+// -----
+
+llvm.func @constrained_fp_call_fcmp_rejected(%arg0: f32) -> i1 {
+ // expected-error at +1 {{intrinsic llvm.experimental.constrained.fcmp.f32 is a constrained FP compare and is not supported by this op}}
+ %0 = llvm.intr.experimental.constrained_fp_call "llvm.experimental.constrained.fcmp.f32"(%arg0, %arg0) ignore : (f32, f32) -> i1
+ llvm.return %0 : i1
+}
+
+// -----
+
+llvm.func @constrained_fp_call_missing_rounding(%arg0: f32) -> f32 {
+ // expected-error at +1 {{intrinsic llvm.experimental.constrained.cos.f32 requires a rounding mode attribute}}
+ %0 = llvm.intr.experimental.constrained_fp_call "llvm.experimental.constrained.cos.f32"(%arg0) ignore : (f32) -> f32
+ llvm.return %0 : f32
+}
+
+// -----
+
+llvm.func @constrained_fp_call_unexpected_rounding(%arg0: f64) -> f64 {
+ // expected-error at +1 {{intrinsic llvm.experimental.constrained.maximum.f64 does not take a rounding mode attribute}}
+ %0 = llvm.intr.experimental.constrained_fp_call "llvm.experimental.constrained.maximum.f64"(%arg0, %arg0) towardzero ignore : (f64, f64) -> f64
+ llvm.return %0 : f64
+}
+
+// -----
+
llvm.func @foo()
llvm.func @wrong_number_of_bundle_tags() {
%0 = llvm.mlir.constant(0 : i32) : i32
diff --git a/mlir/test/Dialect/LLVMIR/roundtrip.mlir b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
index 83d1f1b8e2884..a9861ca756499 100644
--- a/mlir/test/Dialect/LLVMIR/roundtrip.mlir
+++ b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
@@ -902,6 +902,23 @@ llvm.func @experimental_constrained_fptrunc(%in: f64) {
llvm.return
}
+// CHECK-LABEL: @experimental_constrained_fp_call
+llvm.func @experimental_constrained_fp_call(%s: f32, %d: f64, %p: i32) {
+ // CHECK: llvm.intr.experimental.constrained_fp_call "llvm.experimental.constrained.cos.f32"(%{{.*}}) towardzero ignore : (f32) -> f32
+ %0 = llvm.intr.experimental.constrained_fp_call
+ "llvm.experimental.constrained.cos.f32"(%s) towardzero ignore
+ : (f32) -> f32
+ // CHECK: llvm.intr.experimental.constrained_fp_call "llvm.experimental.constrained.maximum.f64"(%{{.*}}, %{{.*}}) strict : (f64, f64) -> f64
+ %1 = llvm.intr.experimental.constrained_fp_call
+ "llvm.experimental.constrained.maximum.f64"(%d, %d) strict
+ : (f64, f64) -> f64
+ // CHECK: llvm.intr.experimental.constrained_fp_call "llvm.experimental.constrained.powi.f32"(%{{.*}}, %{{.*}}) tonearest ignore : (f32, i32) -> f32
+ %2 = llvm.intr.experimental.constrained_fp_call
+ "llvm.experimental.constrained.powi.f32"(%s, %p) tonearest ignore
+ : (f32, i32) -> f32
+ llvm.return
+}
+
// CHECK: llvm.func @tail_call_target() -> i32
llvm.func @tail_call_target() -> i32
diff --git a/mlir/test/Target/LLVMIR/Import/import-failure.ll b/mlir/test/Target/LLVMIR/Import/import-failure.ll
index b468a3e95c907..86760e26c88a5 100644
--- a/mlir/test/Target/LLVMIR/Import/import-failure.ll
+++ b/mlir/test/Target/LLVMIR/Import/import-failure.ll
@@ -456,3 +456,23 @@ bb1:
!91885 = !{!91886, !91887}
!91886 = !{i32 10000, i64 86427, i32 1}
!91887 = !{i32 100000, i64 86427, i32 1}
+
+; // -----
+
+; CHECK: error: constrained FP compare intrinsic 'llvm.experimental.constrained.fcmp.f32' is not supported by the LLVM dialect importer
+define i1 @constrained_fcmp(float %s) {
+ %r = call i1 @llvm.experimental.constrained.fcmp.f32(float %s, float %s, metadata !"oeq", metadata !"fpexcept.ignore")
+ ret i1 %r
+}
+
+declare i1 @llvm.experimental.constrained.fcmp.f32(float, float, metadata, metadata)
+
+; // -----
+
+; CHECK: error: constrained FP compare intrinsic 'llvm.experimental.constrained.fcmps.f32' is not supported by the LLVM dialect importer
+define i1 @constrained_fcmps(float %s) {
+ %r = call i1 @llvm.experimental.constrained.fcmps.f32(float %s, float %s, metadata !"oeq", metadata !"fpexcept.ignore")
+ ret i1 %r
+}
+
+declare i1 @llvm.experimental.constrained.fcmps.f32(float, float, metadata, metadata)
diff --git a/mlir/test/Target/LLVMIR/Import/intrinsic.ll b/mlir/test/Target/LLVMIR/Import/intrinsic.ll
index f79d09aa3d633..0a13f31089d0c 100644
--- a/mlir/test/Target/LLVMIR/Import/intrinsic.ll
+++ b/mlir/test/Target/LLVMIR/Import/intrinsic.ll
@@ -1205,6 +1205,26 @@ define void @experimental_constrained_fpext(float %s, <4 x float> %v) {
ret void
}
+; CHECK-LABEL: experimental_constrained_fp_call
+define void @experimental_constrained_fp_call(float %s, <4 x float> %v, double %d, i32 %p) {
+ ; Unary constrained intrinsic with rounding mode + exception behavior.
+ ; CHECK: llvm.intr.experimental.constrained_fp_call "llvm.experimental.constrained.cos.f32"(%{{.*}}) towardzero ignore : (f32) -> f32
+ %1 = call float @llvm.experimental.constrained.cos.f32(float %s, metadata !"round.towardzero", metadata !"fpexcept.ignore")
+ ; Vector variant.
+ ; CHECK: llvm.intr.experimental.constrained_fp_call "llvm.experimental.constrained.sin.v4f32"(%{{.*}}) dynamic maytrap : (vector<4xf32>) -> vector<4xf32>
+ %2 = call <4 x float> @llvm.experimental.constrained.sin.v4f32(<4 x float> %v, metadata !"round.dynamic", metadata !"fpexcept.maytrap")
+ ; Constrained intrinsic with no rounding mode operand.
+ ; CHECK: llvm.intr.experimental.constrained_fp_call "llvm.experimental.constrained.maximum.f64"(%{{.*}}, %{{.*}}) strict : (f64, f64) -> f64
+ %3 = call double @llvm.experimental.constrained.maximum.f64(double %d, double %d, metadata !"fpexcept.strict")
+ ; Result type different from operand type and no rounding mode.
+ ; CHECK: llvm.intr.experimental.constrained_fp_call "llvm.experimental.constrained.fptosi.i32.f32"(%{{.*}}) ignore : (f32) -> i32
+ %4 = call i32 @llvm.experimental.constrained.fptosi.i32.f32(float %s, metadata !"fpexcept.ignore")
+ ; Mixed floating-point and integer operands.
+ ; CHECK: llvm.intr.experimental.constrained_fp_call "llvm.experimental.constrained.powi.f32"(%{{.*}}, %{{.*}}) tonearest ignore : (f32, i32) -> f32
+ %5 = call float @llvm.experimental.constrained.powi.f32(float %s, i32 %p, metadata !"round.tonearest", metadata !"fpexcept.ignore")
+ ret void
+}
+
; CHECK-LABEL: llvm.func @ucmp
define i2 @ucmp(i32 %a, i32 %b) {
; CHECK: %{{.*}} = llvm.intr.ucmp(%{{.*}}, %{{.*}}) : (i32, i32) -> i2
@@ -1501,6 +1521,11 @@ declare <4 x half> @llvm.experimental.constrained.fptrunc.v4f16.v4f64(<4 x doubl
declare float @llvm.experimental.constrained.fptrunc.f32.f64(double, metadata, metadata)
declare <4 x double> @llvm.experimental.constrained.fpext.v4f64.v4f32(<4 x float>, metadata)
declare double @llvm.experimental.constrained.fpext.f64.f32(float, metadata)
+declare float @llvm.experimental.constrained.cos.f32(float, metadata, metadata)
+declare <4 x float> @llvm.experimental.constrained.sin.v4f32(<4 x float>, metadata, metadata)
+declare double @llvm.experimental.constrained.maximum.f64(double, double, metadata)
+declare i32 @llvm.experimental.constrained.fptosi.i32.f32(float, metadata)
+declare float @llvm.experimental.constrained.powi.f32(float, i32, metadata, metadata)
declare i2 @llvm.ucmp.i2.i32(i32, i32)
declare <4 x i32> @llvm.ucmp.v4i32.v4i32(<4 x i32>, <4 x i32>)
declare i2 @llvm.scmp.i2.i32(i32, i32)
diff --git a/mlir/test/Target/LLVMIR/llvmir-intrinsics.mlir b/mlir/test/Target/LLVMIR/llvmir-intrinsics.mlir
index 11882a0a1d4c6..69ef56271212e 100644
--- a/mlir/test/Target/LLVMIR/llvmir-intrinsics.mlir
+++ b/mlir/test/Target/LLVMIR/llvmir-intrinsics.mlir
@@ -1384,6 +1384,36 @@ llvm.func @experimental_constrained_fpext(%s: f32, %v: vector<4xf32>) {
llvm.return
}
+// CHECK-LABEL: @experimental_constrained_fp_call
+llvm.func @experimental_constrained_fp_call(%s: f32, %v: vector<4xf32>, %d: f64, %p: i32) {
+ // CHECK: call float @llvm.experimental.constrained.cos.f32(
+ // CHECK-SAME: metadata !"round.towardzero", metadata !"fpexcept.ignore"
+ %0 = llvm.intr.experimental.constrained_fp_call
+ "llvm.experimental.constrained.cos"(%s) towardzero ignore
+ : (f32) -> f32
+ // CHECK: call <4 x float> @llvm.experimental.constrained.sin.v4f32(
+ // CHECK-SAME: metadata !"round.dynamic", metadata !"fpexcept.maytrap"
+ %1 = llvm.intr.experimental.constrained_fp_call
+ "llvm.experimental.constrained.sin"(%v) dynamic maytrap
+ : (vector<4xf32>) -> vector<4xf32>
+ // CHECK: call double @llvm.experimental.constrained.maximum.f64(
+ // CHECK-SAME: metadata !"fpexcept.strict"
+ %2 = llvm.intr.experimental.constrained_fp_call
+ "llvm.experimental.constrained.maximum"(%d, %d) strict
+ : (f64, f64) -> f64
+ // CHECK: call i32 @llvm.experimental.constrained.fptosi.i32.f32(
+ // CHECK-SAME: metadata !"fpexcept.ignore"
+ %3 = llvm.intr.experimental.constrained_fp_call
+ "llvm.experimental.constrained.fptosi"(%s) ignore
+ : (f32) -> i32
+ // CHECK: call float @llvm.experimental.constrained.powi.f32(
+ // CHECK-SAME: metadata !"round.tonearest", metadata !"fpexcept.ignore"
+ %4 = llvm.intr.experimental.constrained_fp_call
+ "llvm.experimental.constrained.powi"(%s, %p) tonearest ignore
+ : (f32, i32) -> f32
+ llvm.return
+}
+
// CHECK-LABEL: @ucmp
llvm.func @ucmp(%a: i32, %b: i32) -> i2 {
// CHECK: call i2 @llvm.ucmp.i2.i32
@@ -1614,6 +1644,11 @@ llvm.func @vector_scmp(%a: vector<4 x i32>, %b: vector<4 x i32>) -> vector<4 x i
// CHECK-DAG: declare <4 x half> @llvm.experimental.constrained.fptrunc.v4f16.v4f32(<4 x float>, metadata, metadata)
// CHECK-DAG: declare double @llvm.experimental.constrained.fpext.f64.f32(float, metadata)
// CHECK-DAG: declare <4 x double> @llvm.experimental.constrained.fpext.v4f64.v4f32(<4 x float>, metadata)
+// CHECK-DAG: declare float @llvm.experimental.constrained.cos.f32(float, metadata, metadata)
+// CHECK-DAG: declare <4 x float> @llvm.experimental.constrained.sin.v4f32(<4 x float>, metadata, metadata)
+// CHECK-DAG: declare double @llvm.experimental.constrained.maximum.f64(double, double, metadata)
+// CHECK-DAG: declare i32 @llvm.experimental.constrained.fptosi.i32.f32(float, metadata)
+// CHECK-DAG: declare float @llvm.experimental.constrained.powi.f32(float, i32, metadata, metadata)
// CHECK-DAG: declare range(i2 -1, -2) i2 @llvm.ucmp.i2.i32(i32, i32)
// CHECK-DAG: declare range(i32 -1, 2) <4 x i32> @llvm.ucmp.v4i32.v4i32(<4 x i32>, <4 x i32>)
// CHECK-DAG: declare range(i2 -1, -2) i2 @llvm.scmp.i2.i32(i32, i32)
More information about the Mlir-commits
mailing list