[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


================
@@ -1790,4 +1790,183 @@ def LLVM_AnyMDAttr : AnyAttrOf<[
     LLVM_MDStringAttr, LLVM_MDConstantAttr, LLVM_MDFuncAttr, LLVM_MDNodeAttr],
     "LLVM metadata attribute (md_string, md_const, md_func, or md_node)">;
 
+//===----------------------------------------------------------------------===//
+// FenvAttr
+//===----------------------------------------------------------------------===//
+
+def LLVM_FenvAttr : LLVM_Attr<"Fenv", "fenv"> {
+  let summary = "Describes floating-point environment constraints";
+  let description = [{
+    The `#llvm.fenv` attribute describes constraints on the floating-point
+    handling of a floating-point operation. It can be attached to
+    floating-point operations to capture rounding and exception behavior. All
+    of its parameters are optional.
+
+    - `rounding_mode`: the rounding mode to use. Every value other than
+      `dynamic` specifies a static rounding mode.
+    - `dynamic_rounding_mode`: the known dynamic rounding mode at the point the
+      instruction is executed. Otherwise the behavior is undefined.
+    - `except_mode`: the known exception mode at the point the instruction is
+      executed. Otherwise the behavior is undefined.
+    - `strict_snan`: whether to handle sNaN according to IEEE rules (`true`) or
+      LLVM's relaxed NaN propagation rules (`false`). This flag is only
+      meaningful when `except_mode` is `unmasked` or `unknown`, where it
+      defaults to `true`; when `except_mode` is `masked` the flag is ignored.
+    - `strict_except`: if `false`, any case that would produce an FP exception
+      produces it non-deterministically instead (i.e. it may or may not occur).
+      This means the FP status is written non-deterministically, and, if
+      exceptions are unmasked, the instruction traps non-deterministically.
+      This flag is only meaningful when `except_mode` is `unmasked` or
+      `unknown`, where it defaults to `true`; when `except_mode` is `masked`
+      the flag is ignored.
+
+    When this attribute is present on an operation, the operation is lowered to
+    the corresponding `llvm.experimental.constrained.*` intrinsic during
+    translation to LLVM IR.
+
+    The default setting for each parameter (`dynamic` rounding mode, `unknown`
+    dynamic rounding mode, `masked` exception mode, and, when exceptions are not
+    masked, `true` for both `strict_snan` and `strict_except`) is canonically
+    represented as an unset parameter. Constructing an attribute whose
+    parameters are all set to their defaults therefore yields the same
+    canonical, empty attribute as `#llvm.fenv<>`.
+
+    Carrying the (possibly empty) attribute is *not* the same as having no
+    attribute at all. An operation that carries the attribute is lowered to the
+    corresponding constrained intrinsic, even when every parameter is at its
+    default value (the constrained intrinsic then uses the default rounding mode
+    and exception behavior). An operation with no `#llvm.fenv` attribute is
+    lowered to the regular, unconstrained operation.
+
+    Example:
+    ```mlir
+    #llvm.fenv<dynamic_rounding_mode = tonearest, except_mode = unknown,
+               strict_snan = true, strict_except = true>
+    ```
+  }];
+
+  let parameters = (ins
+    OptionalParameter<"std::optional<::mlir::LLVM::FPRoundingMode>">:$rounding_mode,
+    OptionalParameter<
+        "std::optional<::mlir::LLVM::FPDynamicRoundingMode>">:$dynamic_rounding_mode,
+    OptionalParameter<"std::optional<::mlir::LLVM::FPExceptionMode>">:$except_mode,
+    OptionalParameter<"mlir::BoolAttr">:$strict_snan,
+    OptionalParameter<"mlir::BoolAttr">:$strict_except);
+
+  let assemblyFormat = [{
+    `<` struct($rounding_mode, $dynamic_rounding_mode, $except_mode,
+               $strict_snan, $strict_except) `>`
+  }];
+
+  // Replace the default builder so that constructing the attribute normalizes
+  // any parameter that is set to its default value to "unset". This keeps a
+  // single canonical representation for the default floating-point environment.
+  // Because `skipDefaultBuilders` suppresses the auto-synthesized const builder
+  // call, provide it explicitly. The `DefaultValuedAttr` form below uses a null
+  // attribute as its default, so this simply forwards its argument (a null
+  // attribute when the operation does not carry a `#llvm.fenv` attribute).
+  let skipDefaultBuilders = 1;
+  let constBuilderCall = "$0";
+  let builders = [
+    AttrBuilder<(ins
+        CArg<"std::optional<::mlir::LLVM::FPRoundingMode>",
+             "std::nullopt">:$roundingMode,
+        CArg<"std::optional<::mlir::LLVM::FPDynamicRoundingMode>",
+             "std::nullopt">:$dynamicRoundingMode,
+        CArg<"std::optional<::mlir::LLVM::FPExceptionMode>",
+             "std::nullopt">:$exceptMode,
+        CArg<"mlir::BoolAttr", "{}">:$strictSNaN,
+        CArg<"mlir::BoolAttr", "{}">:$strictExcept), [{
+      if (roundingMode == ::mlir::LLVM::FPRoundingMode::Dynamic)
+        roundingMode = std::nullopt;
+      if (dynamicRoundingMode == ::mlir::LLVM::FPDynamicRoundingMode::Unknown)
+        dynamicRoundingMode = std::nullopt;
+      if (exceptMode == ::mlir::LLVM::FPExceptionMode::Masked)
+        exceptMode = std::nullopt;
+      // `strict_snan` and `strict_except` are only meaningful when exceptions
+      // are not masked, where they default to `true`. When exceptions are
+      // masked (the default) they are ignored, so drop them; otherwise elide a
+      // flag only when it matches the `true` default. After the normalization
+      // above, masked == unset.
+      if (!exceptMode || (strictSNaN && strictSNaN.getValue()))
----------------
adams381 wrote:

Dropping `strict_snan` / `strict_except` under `masked` is fine for the IR you emit today, since masked maps to `ebIgnore` and neither flag is representable there.  What bothers me is that the builder discards them at construction, so `#llvm.fenv<except_mode = masked, strict_except = true>` can't even be held.  Nikita keeps these orthogonal: his default env is `except_mode: masked, strict_except: false`, and `strict_except` still decides whether the fpstatus write is deterministic once something reads fpstatus.  Since the attribute is meant to track his model so only the lowering changes later, I'd keep the flags in storage and collapse them in the lowering / effect queries instead, so a frontend can't lose data round-tripping through the attribute.

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


More information about the Mlir-commits mailing list