[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