[Mlir-commits] [mlir] [mlir][llvmir] Add new support for strict fp handling (PR #205158)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Mon Jun 22 13:51:24 PDT 2026


================
@@ -56,6 +58,229 @@ static llvm::FastMathFlags getFastmathFlags(FastmathFlagsInterface &op) {
   return ret;
 }
 
+//===----------------------------------------------------------------------===//
+// Constrained floating-point lowering (the `#llvm.fenv` attribute).
+//===----------------------------------------------------------------------===//
+
+namespace {
+/// Scoped guard that configures the IRBuilder's constrained floating-point
+/// state, mirroring clang's `CodeGenFunction::CGFPOptionsRAII`. While the state
+/// is enabled, the IRBuilder automatically lowers ordinary floating-point
+/// operations (`CreateFAdd`, `CreateFCmp`, `CreateFPExt`, ...) to the matching
+/// `llvm.experimental.constrained.*` intrinsics. The previous state is restored
+/// on destruction.
+class ConstrainedFPStateRAII {
+public:
+  explicit ConstrainedFPStateRAII(llvm::IRBuilderBase &builder)
+      : builder(builder), oldIsConstrained(builder.getIsFPConstrained()),
+        oldExcept(builder.getDefaultConstrainedExcept()),
+        oldRounding(builder.getDefaultConstrainedRounding()) {}
+
+  ~ConstrainedFPStateRAII() {
+    builder.setIsFPConstrained(oldIsConstrained);
+    builder.setDefaultConstrainedExcept(oldExcept);
+    builder.setDefaultConstrainedRounding(oldRounding);
+  }
+
+  void enable(llvm::RoundingMode rounding, llvm::fp::ExceptionBehavior except) {
+    builder.setIsFPConstrained(true);
+    builder.setDefaultConstrainedRounding(rounding);
+    builder.setDefaultConstrainedExcept(except);
+  }
+
+private:
+  llvm::IRBuilderBase &builder;
+  bool oldIsConstrained;
+  llvm::fp::ExceptionBehavior oldExcept;
+  llvm::RoundingMode oldRounding;
+};
+} // namespace
+
+static llvm::RoundingMode
+getConstrainedRoundingMode(LLVM::FPEnvConstrainedOpInterface fenvOp) {
+  switch (fenvOp.getFenvRoundingMode()) {
+  case LLVM::FPRoundingMode::Dynamic:
+    return llvm::RoundingMode::Dynamic;
+  case LLVM::FPRoundingMode::ToNearest:
+    return llvm::RoundingMode::NearestTiesToEven;
+  case LLVM::FPRoundingMode::Downward:
+    return llvm::RoundingMode::TowardNegative;
+  case LLVM::FPRoundingMode::Upward:
+    return llvm::RoundingMode::TowardPositive;
+  case LLVM::FPRoundingMode::UpwardZero:
+    return llvm::RoundingMode::TowardZero;
+  case LLVM::FPRoundingMode::ToNearestAway:
+    return llvm::RoundingMode::NearestTiesToAway;
+  }
+  llvm_unreachable("unknown LLVM::FPRoundingMode");
+}
+
+static llvm::fp::ExceptionBehavior
+getConstrainedExceptionBehavior(LLVM::FPEnvConstrainedOpInterface fenvOp) {
+  switch (fenvOp.getFenvExceptionMode()) {
+  case LLVM::FPExceptionMode::Masked:
+    return llvm::fp::ebIgnore;
+  case LLVM::FPExceptionMode::Unmasked:
+  case LLVM::FPExceptionMode::Unknown:
+    return fenvOp.getFenvStrictExcept() ? llvm::fp::ebStrict
+                                        : llvm::fp::ebMayTrap;
+  }
+  llvm_unreachable("unknown LLVM::FPExceptionMode");
+}
+
+/// Maps a non-constrained LLVM intrinsic to its constrained counterpart, or
+/// `not_intrinsic` if none exists. Used both for the math intrinsic dialect
+/// operations and for `llvm.call_intrinsic`. `ConstrainedOps.def` is the single
+/// source of truth for this mapping.
+static constexpr llvm::Intrinsic::ID
+getConstrainedIntrinsicFor(llvm::Intrinsic::ID base) {
+  switch (base) {
+#define DAG_FUNCTION(NAME, NARG, ROUND, INTRINSIC, DAGN)                       \
+  case llvm::Intrinsic::NAME:                                                  \
+    return llvm::Intrinsic::INTRINSIC;
+#define FUNCTION(NAME, NARG, ROUND, INTRINSIC)                                 \
+  case llvm::Intrinsic::NAME:                                                  \
+    return llvm::Intrinsic::INTRINSIC;
+#include "llvm/IR/ConstrainedOps.def"
+  default:
+    return llvm::Intrinsic::not_intrinsic;
+  }
+}
+
+/// Emits a constrained floating-point call for a function-style operation: the
+/// math intrinsic dialect operations and `llvm.call_intrinsic`. The original
+/// floating-point operands are taken from \p fpOperands and the result is
+/// mapped to \p result.
+static LogicalResult
+emitConstrainedFPCall(Operation *op, llvm::Intrinsic::ID constrainedID,
+                      ValueRange fpOperands, Value result,
+                      llvm::IRBuilderBase &builder,
+                      LLVM::ModuleTranslation &moduleTranslation) {
+  llvm::Module *mod = builder.GetInsertBlock()->getModule();
+  llvm::LLVMContext &ctx = mod->getContext();
+
+  SmallVector<llvm::Value *> args = moduleTranslation.lookupValues(fpOperands);
+  llvm::Type *resultType = moduleTranslation.convertType(result.getType());
+
+  // Reconstruct the constrained intrinsic signature so the correct overloaded
+  // declaration can be resolved. Constrained intrinsics take one (exception
+  // behavior) or two (rounding mode and exception behavior) trailing metadata
+  // arguments in addition to the original floating-point arguments.
+  SmallVector<llvm::Type *> signatureArgTypes;
+  signatureArgTypes.reserve(args.size() + 2);
+  for (llvm::Value *arg : args)
+    signatureArgTypes.push_back(arg->getType());
+  unsigned numMetadataArgs =
+      llvm::Intrinsic::hasConstrainedFPRoundingModeOperand(constrainedID) ? 2
+                                                                          : 1;
+  llvm::Type *metadataType = llvm::Type::getMetadataTy(ctx);
+  for (unsigned i = 0; i < numMetadataArgs; ++i)
+    signatureArgTypes.push_back(metadataType);
+
+  llvm::FunctionType *signature = llvm::FunctionType::get(
+      resultType, signatureArgTypes, /*isVarArg=*/false);
+
+  std::string errorMsg;
+  llvm::raw_string_ostream errorOS(errorMsg);
+  SmallVector<llvm::Type *> overloadedTypes;
+  if (!llvm::Intrinsic::isSignatureValid(constrainedID, signature,
+                                         overloadedTypes, errorOS)) {
+    return op->emitError("could not resolve constrained intrinsic for the "
+                         "'fenv' attribute: ")
+           << errorMsg;
+  }
+
+  llvm::Function *callee = llvm::Intrinsic::getOrInsertDeclaration(
+      mod, constrainedID, overloadedTypes);
+  // The rounding mode and exception behavior come from the IRBuilder's
+  // constrained floating-point state, configured by ConstrainedFPStateRAII.
+  llvm::Value *call = builder.CreateConstrainedFPCall(callee, args, "");
+  moduleTranslation.mapValue(result, call);
+  return success();
+}
+
+static bool isSignalingPredicate(LLVM::FCmpPredicate predicate) {
----------------
adams381 wrote:

Where does the ordered/unordered split come from here?  The equality/relational split for the ordered predicates matches the C quiet-vs-signaling rule (`oeq`/`one` quiet, `olt`/`ole`/`ogt`/`oge` signaling), but two cases look off: `ord` falls through to the signaling branch while `uno` is listed as non-signaling, even though both are NaN-presence tests and are conventionally quiet; and the unordered relationals (`ult`/`ule`/...) are all classed non-signaling.  A comment plus a couple of test rows (an `ord` and an unordered relational under `strict_snan`) would pin the intent.

https://github.com/llvm/llvm-project/pull/205158


More information about the Mlir-commits mailing list