[Mlir-commits] [mlir] [MLIR][LLVM] Preserve constrained floating-point environment effects (PR #234506)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Wed Oct 21 23:28:11 PDT 2026


https://github.com/sriramshastry updated https://github.com/llvm/llvm-project/pull/234506

>From b1310617523150bd170f6cac30609e55607dc6d4 Mon Sep 17 00:00:00 2001
From: sriram shastry <sriramshastry at gmail.com>
Date: Thu, 22 Oct 2026 06:16:07 +0000
Subject: [PATCH] [MLIR][LLVM] Preserve constrained floating-point environment
 effects

Constrained FP intrinsics can read the rounding mode and update exception
flags. Marking them Pure lets optimizations remove observable exceptions
or reuse results across changes to the floating-point environment.

Replace Pure on the arithmetic, conversion, and comparison bases with a
shared MemoryEffectsOpInterface implementation. Model a read for every
constrained intrinsic and a write unless exception behavior is Ignore.
Remove the unconditional speculation trait.

Unused Ignore operations can still be removed, and adjacent identical
Ignore operations can still be merged. Retaining unused Maytrap operations
is conservative: LLVM permits hiding their exceptions but not adding new
ones.

Add regression tests for DCE, CSE, rounding-mode changes, and loop motion,
including comparisons, conversions, vectors, and all exception modes.

Signed-off-by: sriram shastry <sriramshastry at gmail.com>
---
 .../mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td   |  18 +-
 .../Dialect/LLVMIR/constrained-effects.mlir   | 179 ++++++++++++++++++
 2 files changed, 195 insertions(+), 2 deletions(-)
 create mode 100644 mlir/test/Dialect/LLVMIR/constrained-effects.mlir

diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
index dfd36966644aaa..d6f7e5850e2fb1 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
@@ -452,6 +452,17 @@ def LLVM_LaunderInvariantGroupOp
 
 // Constrained Floating-Point Intrinsics.
 
+defvar constrainedFPEffects = [{
+  void getEffects(
+      ::llvm::SmallVectorImpl<::mlir::SideEffects::EffectInstance<
+          ::mlir::MemoryEffects::Effect>> &effects) {
+    effects.emplace_back(::mlir::MemoryEffects::Read::get());
+    if (getFpExceptionBehaviorAttr().getValue() !=
+        ::mlir::LLVM::FPExceptionBehavior::Ignore)
+      effects.emplace_back(::mlir::MemoryEffects::Write::get());
+  }
+}];
+
 class LLVM_ConstrainedIntr<string mnem, int numArgs,
                            bit overloadedResult, list<int> overloadedOperands,
                            bit hasRoundingMode,
@@ -461,7 +472,8 @@ class LLVM_ConstrainedIntr<string mnem, int numArgs,
                            !cond(!gt(overloadedResult, 0) : [0],
                                  true : []),
                            overloadedOperands,
-                           /*traits=*/[Pure, DeclareOpInterfaceMethods<FPExceptionBehaviorOpInterface>]
+                           /*traits=*/[MemoryEffectsOpInterface,
+                                       DeclareOpInterfaceMethods<FPExceptionBehaviorOpInterface>]
                            # !cond(
                                !gt(hasRoundingMode, 0) : [DeclareOpInterfaceMethods<RoundingModeOpInterface>],
                                true : [])
@@ -542,6 +554,7 @@ class LLVM_ConstrainedIntr<string mnem, int numArgs,
     moduleImport.setFastmathFlagsAttr(inst, op);
     $res = op;
   }];
+  let extraClassDeclaration = constrainedFPEffects;
 }
 
 // Convenience base class for constrained intrinsics that take a single
@@ -620,7 +633,7 @@ class LLVM_ConstrainedFPToIntIntrNoRM<string mnem>
 class LLVM_ConstrainedFCmpIntrBase<string mnem>
     : LLVM_OneResultIntrOp<"experimental.constrained." # mnem,
         /*overloadedResults=*/[], /*overloadedOperands=*/[0],
-        /*traits=*/[Pure, SameTypeOperands,
+        /*traits=*/[MemoryEffectsOpInterface, SameTypeOperands,
           TypesMatchWith<"result type has i1 element type and same shape as "
                          "operands", "arg_0", "res",
                          "::mlir::LLVM::getI1SameShape($_self)">,
@@ -657,6 +670,7 @@ class LLVM_ConstrainedFCmpIntrBase<string mnem>
     moduleImport.setFastmathFlagsAttr(inst, op);
     $res = op;
   }];
+  let extraClassDeclaration = constrainedFPEffects;
 }
 
 def LLVM_ConstrainedFAddIntr
diff --git a/mlir/test/Dialect/LLVMIR/constrained-effects.mlir b/mlir/test/Dialect/LLVMIR/constrained-effects.mlir
new file mode 100644
index 00000000000000..29ae9fff4ec233
--- /dev/null
+++ b/mlir/test/Dialect/LLVMIR/constrained-effects.mlir
@@ -0,0 +1,179 @@
+// RUN: mlir-opt %s -canonicalize -cse -loop-invariant-code-motion | FileCheck %s
+// RUN: mlir-opt %s -cse | FileCheck %s
+
+llvm.func @change_environment()
+
+// CHECK-LABEL: llvm.func @unused_strict
+// CHECK: llvm.intr.experimental.constrained.sqrt {{.*}} tonearest strict
+// CHECK: llvm.return
+llvm.func @unused_strict(%input: f32) {
+  %unused = llvm.intr.experimental.constrained.sqrt %input tonearest strict : f32
+  llvm.return
+}
+
+// CHECK-LABEL: llvm.func @unused_maytrap
+// CHECK: llvm.intr.experimental.constrained.sqrt {{.*}} tonearest maytrap
+// CHECK: llvm.return
+llvm.func @unused_maytrap(%input: f32) {
+  %unused = llvm.intr.experimental.constrained.sqrt %input tonearest maytrap : f32
+  llvm.return
+}
+
+// CHECK-LABEL: llvm.func @unused_ignore
+// CHECK-NOT: llvm.intr.experimental.constrained
+// CHECK: llvm.return
+llvm.func @unused_ignore(%input: f32) {
+  %unused = llvm.intr.experimental.constrained.sqrt %input tonearest ignore : f32
+  llvm.return
+}
+
+// CHECK-LABEL: llvm.func @unused_binary_strict
+// CHECK: llvm.intr.experimental.constrained.fadd {{.*}} dynamic strict
+// CHECK: llvm.return
+llvm.func @unused_binary_strict(%input: f32, %increment: f32) {
+  %unused = llvm.intr.experimental.constrained.fadd %input, %increment dynamic strict : f32
+  llvm.return
+}
+
+// CHECK-LABEL: llvm.func @unused_no_rounding_strict
+// CHECK: llvm.intr.experimental.constrained.ceil {{.*}} strict
+// CHECK: llvm.return
+llvm.func @unused_no_rounding_strict(%input: f32) {
+  %unused = llvm.intr.experimental.constrained.ceil %input strict : f32
+  llvm.return
+}
+
+// CHECK-LABEL: llvm.func @strict_not_cse
+// CHECK: llvm.intr.experimental.constrained.sqrt
+// CHECK: llvm.intr.experimental.constrained.sqrt
+// CHECK: llvm.return
+llvm.func @strict_not_cse(%input: f32) -> f32 {
+  %first = llvm.intr.experimental.constrained.sqrt %input tonearest strict : f32
+  %second = llvm.intr.experimental.constrained.sqrt %input tonearest strict : f32
+  %sum = llvm.fadd %first, %second : f32
+  llvm.return %sum : f32
+}
+
+// CHECK-LABEL: llvm.func @ignore_can_cse
+// CHECK: %[[VALUE:.*]] = llvm.intr.experimental.constrained.sqrt
+// CHECK-NOT: llvm.intr.experimental.constrained.sqrt
+// CHECK: llvm.fadd %[[VALUE]], %[[VALUE]]
+// CHECK: llvm.return
+llvm.func @ignore_can_cse(%input: f32) -> f32 {
+  %first = llvm.intr.experimental.constrained.sqrt %input tonearest ignore : f32
+  %second = llvm.intr.experimental.constrained.sqrt %input tonearest ignore : f32
+  %sum = llvm.fadd %first, %second : f32
+  llvm.return %sum : f32
+}
+
+// CHECK-LABEL: llvm.func @dynamic_rounding_not_cse
+// CHECK: llvm.intr.experimental.constrained.fadd {{.*}} dynamic ignore
+// CHECK: llvm.call @change_environment
+// CHECK: llvm.intr.experimental.constrained.fadd {{.*}} dynamic ignore
+// CHECK: llvm.return
+llvm.func @dynamic_rounding_not_cse(%input: f32, %increment: f32) -> f32 {
+  %first = llvm.intr.experimental.constrained.fadd %input, %increment dynamic ignore : f32
+  llvm.call @change_environment() : () -> ()
+  %second = llvm.intr.experimental.constrained.fadd %input, %increment dynamic ignore : f32
+  %difference = llvm.fsub %second, %first : f32
+  llvm.return %difference : f32
+}
+
+// CHECK-LABEL: func.func @strict_stays_in_loop
+// CHECK-NOT: llvm.intr.experimental.constrained.sqrt
+// CHECK: scf.for
+// CHECK: llvm.intr.experimental.constrained.sqrt {{.*}} tonearest strict
+// CHECK: return
+func.func @strict_stays_in_loop(%lower: index, %upper: index, %step: index, %input: f32) {
+  scf.for %index = %lower to %upper step %step {
+    %unused = llvm.intr.experimental.constrained.sqrt %input tonearest strict : f32
+  }
+  return
+}
+
+// CHECK-LABEL: llvm.func @unused_quiet_comparison_strict
+// CHECK: llvm.intr.experimental.constrained.fcmp oeq {{.*}} strict
+// CHECK: llvm.return
+llvm.func @unused_quiet_comparison_strict(%left: f32, %right: f32) {
+  %unused = llvm.intr.experimental.constrained.fcmp "oeq" %left, %right strict : f32
+  llvm.return
+}
+
+// CHECK-LABEL: llvm.func @unused_signaling_comparison_strict
+// CHECK: llvm.intr.experimental.constrained.fcmps oeq {{.*}} strict
+// CHECK: llvm.return
+llvm.func @unused_signaling_comparison_strict(%left: f32, %right: f32) {
+  %unused = llvm.intr.experimental.constrained.fcmps "oeq" %left, %right strict : f32
+  llvm.return
+}
+
+// CHECK-LABEL: llvm.func @unused_signaling_comparison_maytrap
+// CHECK: llvm.intr.experimental.constrained.fcmps oeq {{.*}} maytrap
+// CHECK: llvm.return
+llvm.func @unused_signaling_comparison_maytrap(%left: f32, %right: f32) {
+  %unused = llvm.intr.experimental.constrained.fcmps "oeq" %left, %right maytrap : f32
+  llvm.return
+}
+
+// CHECK-LABEL: llvm.func @unused_signaling_comparison_ignore
+// CHECK-NOT: llvm.intr.experimental.constrained
+// CHECK: llvm.return
+llvm.func @unused_signaling_comparison_ignore(%left: f32, %right: f32) {
+  %unused = llvm.intr.experimental.constrained.fcmps "oeq" %left, %right ignore : f32
+  llvm.return
+}
+
+// CHECK-LABEL: llvm.func @unused_conversion_strict
+// CHECK: llvm.intr.experimental.constrained.fptosi {{.*}} strict
+// CHECK: llvm.return
+llvm.func @unused_conversion_strict(%input: f64) {
+  %unused = llvm.intr.experimental.constrained.fptosi %input strict : f64 to i32
+  llvm.return
+}
+
+// CHECK-LABEL: llvm.func @unused_truncation_strict
+// CHECK: llvm.intr.experimental.constrained.fptrunc {{.*}} dynamic strict
+// CHECK: llvm.return
+llvm.func @unused_truncation_strict(%input: f64) {
+  %unused = llvm.intr.experimental.constrained.fptrunc %input dynamic strict : f64 to f32
+  llvm.return
+}
+
+// CHECK-LABEL: llvm.func @unused_vector_maytrap
+// CHECK: llvm.intr.experimental.constrained.sqrt {{.*}} upward maytrap
+// CHECK: llvm.return
+llvm.func @unused_vector_maytrap(%input: vector<4xf32>) {
+  %unused = llvm.intr.experimental.constrained.sqrt %input upward maytrap : vector<4xf32>
+  llvm.return
+}
+
+// CHECK-LABEL: llvm.func @unused_dynamic_ignore
+// CHECK-NOT: llvm.intr.experimental.constrained
+// CHECK: llvm.return
+llvm.func @unused_dynamic_ignore(%input: f64) {
+  %unused = llvm.intr.experimental.constrained.sqrt %input dynamic ignore : f64
+  llvm.return
+}
+
+// CHECK-LABEL: llvm.func @maytrap_not_cse
+// CHECK: llvm.intr.experimental.constrained.sqrt
+// CHECK: llvm.intr.experimental.constrained.sqrt
+// CHECK: llvm.return
+llvm.func @maytrap_not_cse(%input: f64) -> f64 {
+  %first = llvm.intr.experimental.constrained.sqrt %input downward maytrap : f64
+  %second = llvm.intr.experimental.constrained.sqrt %input downward maytrap : f64
+  %sum = llvm.fadd %first, %second : f64
+  llvm.return %sum : f64
+}
+
+// CHECK-LABEL: llvm.func @comparison_ignore_can_cse
+// CHECK: %[[COMPARE:.*]] = llvm.intr.experimental.constrained.fcmps
+// CHECK-NOT: llvm.intr.experimental.constrained.fcmps
+// CHECK: llvm.xor %[[COMPARE]], %[[COMPARE]]
+// CHECK: llvm.return
+llvm.func @comparison_ignore_can_cse(%left: vector<4xf32>, %right: vector<4xf32>) -> vector<4xi1> {
+  %first = llvm.intr.experimental.constrained.fcmps "olt" %left, %right ignore : vector<4xf32>
+  %second = llvm.intr.experimental.constrained.fcmps "olt" %left, %right ignore : vector<4xf32>
+  %difference = llvm.xor %first, %second : vector<4xi1>
+  llvm.return %difference : vector<4xi1>
+}



More information about the Mlir-commits mailing list