[llvm] [ConstraintElim] Strengthen flags at the closest dominator of all uses. (PR #220918)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 8 01:59:56 PDT 2026


https://github.com/fhahn updated https://github.com/llvm/llvm-project/pull/220918

>From cc5b5133cd66f3ebd60519222d109b24baa43b51 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Wed, 2 Sep 2026 21:55:34 +0100
Subject: [PATCH 1/3] [ConstraintElim] Add tests for flag-strengthening at use
 sites.

---
 .../strengthen-flags-at-uses.ll               | 393 ++++++++++++++++++
 1 file changed, 393 insertions(+)
 create mode 100644 llvm/test/Transforms/ConstraintElimination/strengthen-flags-at-uses.ll

diff --git a/llvm/test/Transforms/ConstraintElimination/strengthen-flags-at-uses.ll b/llvm/test/Transforms/ConstraintElimination/strengthen-flags-at-uses.ll
new file mode 100644
index 0000000000000..1136c3188672e
--- /dev/null
+++ b/llvm/test/Transforms/ConstraintElimination/strengthen-flags-at-uses.ll
@@ -0,0 +1,393 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=constraint-elimination -S %s | FileCheck %s
+
+define i64 @sub_hoisted_single_use(i64 %a, i64 %b) {
+; CHECK-LABEL: define i64 @sub_hoisted_single_use(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    br i1 [[C]], label %[[IF_END:.*]], label %[[EXIT:.*]]
+; CHECK:       [[IF_END]]:
+; CHECK-NEXT:    ret i64 [[SUB]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret i64 0
+;
+entry:
+  %c = icmp uge i64 %a, %b
+  %sub = sub i64 %a, %b
+  br i1 %c, label %if.end, label %exit
+
+if.end:
+  ret i64 %sub
+
+exit:
+  ret i64 0
+}
+
+define i64 @sub_hoisted_use_outside(i64 %a, i64 %b) {
+; CHECK-LABEL: define i64 @sub_hoisted_use_outside(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    br i1 [[C]], label %[[IF_END:.*]], label %[[EXIT:.*]]
+; CHECK:       [[IF_END]]:
+; CHECK-NEXT:    ret i64 [[SUB]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret i64 [[SUB]]
+;
+entry:
+  %c = icmp uge i64 %a, %b
+  %sub = sub i64 %a, %b
+  br i1 %c, label %if.end, label %exit
+
+if.end:
+  ret i64 %sub
+
+exit:
+  ret i64 %sub
+}
+
+define i64 @sub_anchored_after_assume(i64 %a, i64 %b) {
+; CHECK-LABEL: define i64 @sub_anchored_after_assume(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    br label %[[MID:.*]]
+; CHECK:       [[MID]]:
+; CHECK-NEXT:    call void @barrier()
+; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT:    ret i64 [[SUB]]
+;
+entry:
+  %sub = sub i64 %a, %b
+  br label %mid
+
+mid:
+  call void @barrier()
+  %c = icmp uge i64 %a, %b
+  call void @llvm.assume(i1 %c)
+  ret i64 %sub
+}
+
+define i64 @sub_used_before_assume(i64 %a, i64 %b) {
+; CHECK-LABEL: define i64 @sub_used_before_assume(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    br label %[[MID:.*]]
+; CHECK:       [[MID]]:
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT:    ret i64 [[SUB]]
+;
+entry:
+  %sub = sub i64 %a, %b
+  br label %mid
+
+mid:
+  call void @use(i64 %sub)
+  %c = icmp uge i64 %a, %b
+  call void @llvm.assume(i1 %c)
+  ret i64 %sub
+}
+
+; The use in %other is not dominated by the assume, so no flag can be inferred.
+define i64 @sub_use_not_dominated_by_assume(i64 %a, i64 %b, i1 %cc) {
+; CHECK-LABEL: define i64 @sub_use_not_dominated_by_assume(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]], i1 [[CC:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    br i1 [[CC]], label %[[MID:.*]], label %[[OTHER:.*]]
+; CHECK:       [[MID]]:
+; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT:    ret i64 [[SUB]]
+; CHECK:       [[OTHER]]:
+; CHECK-NEXT:    ret i64 [[SUB]]
+;
+entry:
+  %sub = sub i64 %a, %b
+  br i1 %cc, label %mid, label %other
+
+mid:
+  %c = icmp uge i64 %a, %b
+  call void @llvm.assume(i1 %c)
+  ret i64 %sub
+
+other:
+  ret i64 %sub
+}
+
+define i64 @mul_hoisted_single_use(i64 %a) {
+; CHECK-LABEL: define i64 @mul_hoisted_single_use(
+; CHECK-SAME: i64 [[A:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[MUL:%.*]] = mul nsw i64 [[A]], 4
+; CHECK-NEXT:    [[C:%.*]] = icmp sge i64 [[A]], 0
+; CHECK-NEXT:    br i1 [[C]], label %[[IF_END:.*]], label %[[EXIT:.*]]
+; CHECK:       [[IF_END]]:
+; CHECK-NEXT:    ret i64 [[MUL]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret i64 0
+;
+entry:
+  %mul = mul nsw i64 %a, 4
+  %c = icmp sge i64 %a, 0
+  br i1 %c, label %if.end, label %exit
+
+if.end:
+  ret i64 %mul
+
+exit:
+  ret i64 0
+}
+
+define void @sub_15_uses_fact_at_common_dominator(i64 %a, i64 %b) {
+; CHECK-LABEL: define void @sub_15_uses_fact_at_common_dominator(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    br label %[[USE_BB:.*]]
+; CHECK:       [[USE_BB]]:
+; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  %sub = sub i64 %a, %b
+  br label %use.bb
+
+use.bb:
+  %c = icmp uge i64 %a, %b
+  call void @llvm.assume(i1 %c)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  ret void
+}
+
+; Same as @sub_15_uses_fact_at_common_dominator, but with 16 uses the scan gives
+; up and the flags are checked at %sub itself, which the assume does not
+; dominate. No flag can be inferred.
+define void @sub_16_uses_fact_at_common_dominator(i64 %a, i64 %b) {
+; CHECK-LABEL: define void @sub_16_uses_fact_at_common_dominator(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    br label %[[USE_BB:.*]]
+; CHECK:       [[USE_BB]]:
+; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  %sub = sub i64 %a, %b
+  br label %use.bb
+
+use.bb:
+  %c = icmp uge i64 %a, %b
+  call void @llvm.assume(i1 %c)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  ret void
+}
+
+define void @sub_16_uses_fact_at_def(i64 %a, i64 %b) {
+; CHECK-LABEL: define void @sub_16_uses_fact_at_def(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[A]], [[B]]
+; CHECK-NEXT:    br label %[[USE_BB:.*]]
+; CHECK:       [[USE_BB]]:
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  %c = icmp uge i64 %a, %b
+  call void @llvm.assume(i1 %c)
+  %sub = sub i64 %a, %b
+  br label %use.bb
+
+use.bb:
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  call void @use(i64 %sub)
+  ret void
+}
+
+; The only use of %sub is in an unreachable block.
+define i64 @sub_single_use_in_unreachable_block(i64 %a, i64 %b) {
+; CHECK-LABEL: define i64 @sub_single_use_in_unreachable_block(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[A]], [[B]]
+; CHECK-NEXT:    ret i64 0
+; CHECK:       [[UNREACHABLE_BB:.*:]]
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    ret i64 0
+;
+entry:
+  %c = icmp uge i64 %a, %b
+  call void @llvm.assume(i1 %c)
+  %sub = sub i64 %a, %b
+  ret i64 0
+
+unreachable.bb:
+  call void @use(i64 %sub)
+  ret i64 0
+}
+
+; As above, but with the uses spread over two unreachable blocks.
+define i64 @sub_uses_in_two_unreachable_blocks(i64 %a, i64 %b) {
+; CHECK-LABEL: define i64 @sub_uses_in_two_unreachable_blocks(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[A]], [[B]]
+; CHECK-NEXT:    ret i64 0
+; CHECK:       [[UNREACHABLE_BB_1:.*:]]
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    br label %[[UNREACHABLE_BB_2:.*]]
+; CHECK:       [[UNREACHABLE_BB_2]]:
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    ret i64 0
+;
+entry:
+  %c = icmp uge i64 %a, %b
+  call void @llvm.assume(i1 %c)
+  %sub = sub i64 %a, %b
+  ret i64 0
+
+unreachable.bb.1:
+  call void @use(i64 %sub)
+  br label %unreachable.bb.2
+
+unreachable.bb.2:
+  call void @use(i64 %sub)
+  ret i64 0
+}
+
+define i64 @sub_use_in_unreachable_block_ignored(i64 %a, i64 %b) {
+; CHECK-LABEL: define i64 @sub_use_in_unreachable_block_ignored(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    br label %[[MID:.*]]
+; CHECK:       [[MID]]:
+; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT:    ret i64 [[SUB]]
+; CHECK:       [[UNREACHABLE_BB:.*:]]
+; CHECK-NEXT:    call void @use(i64 [[SUB]])
+; CHECK-NEXT:    ret i64 0
+;
+entry:
+  %sub = sub i64 %a, %b
+  br label %mid
+
+mid:
+  %c = icmp uge i64 %a, %b
+  call void @llvm.assume(i1 %c)
+  ret i64 %sub
+
+unreachable.bb:
+  call void @use(i64 %sub)
+  ret i64 0
+}
+
+declare void @use(i64)
+declare void @barrier()

>From 1f84154c7c706d2f6a4f5dea8e19f8c42e2ecb8a Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Wed, 2 Sep 2026 14:42:47 +0100
Subject: [PATCH 2/3] [ConstraintElim] Strengthen flags at the closest
 dominator of all uses.

Flags are currently only strengthened using the facts that hold on entry
to the block defining the instruction. This overly pessimistic. It is
sufficient to check at the closest instruction dominating all uses.

Extend FactOrCheck to carry this point as context instruction (in union
with the mutually exclusive DoesHold to avoid size growth) and retrieve
the common dominating instruction when queuing instructions for flag
strengthening.

Note that we already have some duplication here in general: we have
logic for flag inference explicitly but also implicitly in various
decompose rules. With this change, the gap between them closes
considerably, but a small gap still remains: decompose inference works
with facts at the specific use site where the query is performed, which
we cannot completely match with the nearest dominator.

I am planning to investigate if there's a better way to at least unify
the inference logic in a single place, but ideally we would only query
per-use in decompose when we know that it could improve results. One
example where the flag strengthening logic should always be sufficient
is if there's a single use.

This strengthens flag inference in a number of real-world cases, as well
as enables additional folds due stronger flags:
https://github.com/dtcxzyw/llvm-opt-benchmark-nightly/pull/1171

Compile-time impact is in the noise
https://llvm-compile-time-tracker.com/compare.php?from=11bc6ce46e227718c3b54aa6889d7458656e8ab4&to=6f67fdcdb62a5fe56060cb266448db8b763e13d7&stat=instructions:u
---
 .../Scalar/ConstraintElimination.cpp          | 58 ++++++++++++++-----
 .../monotonic-int-phis-signed.ll              |  6 +-
 .../strengthen-flags-at-uses.ll               | 16 ++---
 3 files changed, 55 insertions(+), 25 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index aef5fca40f7d1..b7e8b22cc6a94 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -75,6 +75,24 @@ static Instruction *getContextInstForUse(Use &U) {
   return UserI;
 }
 
+/// Returns the closest program point dominating all uses of \p I, or nullptr if
+/// \p I has no uses or all of them are in unreachable blocks.
+static Instruction *findCommonDominatorOfUses(Instruction &I,
+                                              DominatorTree &DT) {
+  Instruction *CommonDom = nullptr;
+  unsigned NumUses = 0;
+  for (Use &U : I.uses()) {
+    ++NumUses;
+    if (NumUses == 16)
+      return nullptr;
+    Instruction *UserI = getContextInstForUse(U);
+    CommonDom =
+        CommonDom ? DT.findNearestCommonDominator(CommonDom, UserI) : UserI;
+  }
+  // Uses in unreachable blocks are not in the dominator tree.
+  return CommonDom && DT.getNode(CommonDom->getParent()) ? CommonDom : nullptr;
+}
+
 namespace {
 using Entry = ConstraintSystem::Entry;
 using RowTy = ConstraintSystem::RowTy;
@@ -111,21 +129,28 @@ struct FactOrCheck {
     ConditionTy Cond;
   };
 
-  /// A pre-condition that must hold for the current fact to be added to the
-  /// system.
-  ConditionTy DoesHold;
+  union {
+    /// A pre-condition that must hold for the current fact to be added to the
+    /// system. Only used by condition facts.
+    ConditionTy DoesHold;
+
+    /// Context instruction for the point where conditions are checked for
+    /// InstCheck simplifications.
+    Instruction *ContextInst;
+  };
 
   unsigned NumIn;
   unsigned NumOut;
   EntryTy Ty;
 
-  FactOrCheck(EntryTy Ty, DomTreeNode *DTN, Instruction *Inst)
-      : Inst(Inst), NumIn(DTN->getDFSNumIn()), NumOut(DTN->getDFSNumOut()),
-        Ty(Ty) {}
+  FactOrCheck(EntryTy Ty, DomTreeNode *DTN, Instruction *Inst,
+              Instruction *ContextInst = nullptr)
+      : Inst(Inst), ContextInst(ContextInst ? ContextInst : Inst),
+        NumIn(DTN->getDFSNumIn()), NumOut(DTN->getDFSNumOut()), Ty(Ty) {}
 
   FactOrCheck(DomTreeNode *DTN, Use *U)
-      : U(U), NumIn(DTN->getDFSNumIn()), NumOut(DTN->getDFSNumOut()),
-        Ty(EntryTy::UseCheck) {}
+      : U(U), ContextInst(nullptr), NumIn(DTN->getDFSNumIn()),
+        NumOut(DTN->getDFSNumOut()), Ty(EntryTy::UseCheck) {}
 
   FactOrCheck(DomTreeNode *DTN, CmpPredicate Pred, Value *Op0, Value *Op1,
               ConditionTy Precond = {})
@@ -146,8 +171,11 @@ struct FactOrCheck {
     return FactOrCheck(DTN, U);
   }
 
-  static FactOrCheck getCheck(DomTreeNode *DTN, Instruction *I) {
-    return FactOrCheck(EntryTy::InstCheck, DTN, I);
+  static FactOrCheck getCheck(DomTreeNode *DTN, Instruction *I,
+                              Instruction *ContextInst = nullptr) {
+    assert((ContextInst ? ContextInst : I)->getParent() == DTN->getBlock() &&
+           "anchoring instruction must be in DTN's block");
+    return FactOrCheck(EntryTy::InstCheck, DTN, I, ContextInst);
   }
 
   bool isCheck() const {
@@ -158,7 +186,7 @@ struct FactOrCheck {
     assert(!isConditionFact());
     if (Ty == EntryTy::UseCheck)
       return getContextInstForUse(*U);
-    return Inst;
+    return ContextInst;
   }
 
   Instruction *getInstructionToSimplify() const {
@@ -1543,10 +1571,12 @@ void State::addInfoFor(BasicBlock &BB) {
         WorkList.push_back(FactOrCheck::getInstFact(DT.getNode(&BB), BO));
     }
 
-    // Queue instructions whose flags may be strengthened based on the facts
-    // that hold on entry to BB.
+    // Queue instructions whose flags may be strengthened, checked at the
+    // closest point dominating all uses.
     if (canStrengthenFlags(&I))
-      WorkList.push_back(FactOrCheck::getCheck(DT.getNode(&BB), &I));
+      if (Instruction *CommonDom = findCommonDominatorOfUses(I, DT))
+        WorkList.push_back(FactOrCheck::getCheck(
+            DT.getNode(CommonDom->getParent()), &I, CommonDom));
 
     GuaranteedToExecute &= isGuaranteedToTransferExecutionToSuccessor(&I);
   }
diff --git a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-signed.ll b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-signed.ll
index a5c0702896efd..ff27d0f8007db 100644
--- a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-signed.ll
+++ b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-signed.ll
@@ -46,7 +46,7 @@ define void @signed_iv_step_4(i64 %count) {
 ; CHECK-LABEL: define void @signed_iv_step_4(
 ; CHECK-SAME: i64 [[COUNT:%.*]]) {
 ; CHECK-NEXT:  entry:
-; CHECK-NEXT:    [[END:%.*]] = shl nsw i64 [[COUNT]], 2
+; CHECK-NEXT:    [[END:%.*]] = shl nuw nsw i64 [[COUNT]], 2
 ; CHECK-NEXT:    [[PRECOND:%.*]] = icmp sgt i64 [[COUNT]], -1
 ; CHECK-NEXT:    br i1 [[PRECOND]], label [[LOOP:%.*]], label [[EXIT:%.*]]
 ; CHECK:       loop:
@@ -130,7 +130,7 @@ define void @signed_iv_step_4_start_4(i64 %count) {
 ; CHECK-LABEL: define void @signed_iv_step_4_start_4(
 ; CHECK-SAME: i64 [[COUNT:%.*]]) {
 ; CHECK-NEXT:  entry:
-; CHECK-NEXT:    [[END:%.*]] = shl nsw i64 [[COUNT]], 2
+; CHECK-NEXT:    [[END:%.*]] = shl nuw nsw i64 [[COUNT]], 2
 ; CHECK-NEXT:    [[PRECOND:%.*]] = icmp sgt i64 [[COUNT]], 0
 ; CHECK-NEXT:    br i1 [[PRECOND]], label [[LOOP:%.*]], label [[EXIT:%.*]]
 ; CHECK:       loop:
@@ -171,7 +171,7 @@ define void @signed_iv_step_4_start_4_missing_precond(i64 %count) {
 ; CHECK-LABEL: define void @signed_iv_step_4_start_4_missing_precond(
 ; CHECK-SAME: i64 [[COUNT:%.*]]) {
 ; CHECK-NEXT:  entry:
-; CHECK-NEXT:    [[END:%.*]] = shl nsw i64 [[COUNT]], 2
+; CHECK-NEXT:    [[END:%.*]] = shl nuw nsw i64 [[COUNT]], 2
 ; CHECK-NEXT:    [[PRECOND:%.*]] = icmp sgt i64 [[COUNT]], -1
 ; CHECK-NEXT:    br i1 [[PRECOND]], label [[LOOP:%.*]], label [[EXIT:%.*]]
 ; CHECK:       loop:
diff --git a/llvm/test/Transforms/ConstraintElimination/strengthen-flags-at-uses.ll b/llvm/test/Transforms/ConstraintElimination/strengthen-flags-at-uses.ll
index 1136c3188672e..afbcf2b039d61 100644
--- a/llvm/test/Transforms/ConstraintElimination/strengthen-flags-at-uses.ll
+++ b/llvm/test/Transforms/ConstraintElimination/strengthen-flags-at-uses.ll
@@ -6,7 +6,7 @@ define i64 @sub_hoisted_single_use(i64 %a, i64 %b) {
 ; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
-; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[A]], [[B]]
 ; CHECK-NEXT:    br i1 [[C]], label %[[IF_END:.*]], label %[[EXIT:.*]]
 ; CHECK:       [[IF_END]]:
 ; CHECK-NEXT:    ret i64 [[SUB]]
@@ -53,7 +53,7 @@ define i64 @sub_anchored_after_assume(i64 %a, i64 %b) {
 ; CHECK-LABEL: define i64 @sub_anchored_after_assume(
 ; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[A]], [[B]]
 ; CHECK-NEXT:    br label %[[MID:.*]]
 ; CHECK:       [[MID]]:
 ; CHECK-NEXT:    call void @barrier()
@@ -126,7 +126,7 @@ define i64 @mul_hoisted_single_use(i64 %a) {
 ; CHECK-LABEL: define i64 @mul_hoisted_single_use(
 ; CHECK-SAME: i64 [[A:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[MUL:%.*]] = mul nsw i64 [[A]], 4
+; CHECK-NEXT:    [[MUL:%.*]] = mul nuw nsw i64 [[A]], 4
 ; CHECK-NEXT:    [[C:%.*]] = icmp sge i64 [[A]], 0
 ; CHECK-NEXT:    br i1 [[C]], label %[[IF_END:.*]], label %[[EXIT:.*]]
 ; CHECK:       [[IF_END]]:
@@ -150,7 +150,7 @@ define void @sub_15_uses_fact_at_common_dominator(i64 %a, i64 %b) {
 ; CHECK-LABEL: define void @sub_15_uses_fact_at_common_dominator(
 ; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[A]], [[B]]
 ; CHECK-NEXT:    br label %[[USE_BB:.*]]
 ; CHECK:       [[USE_BB]]:
 ; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
@@ -259,7 +259,7 @@ define void @sub_16_uses_fact_at_def(i64 %a, i64 %b) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
 ; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
-; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[A]], [[B]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
 ; CHECK-NEXT:    br label %[[USE_BB:.*]]
 ; CHECK:       [[USE_BB]]:
 ; CHECK-NEXT:    call void @use(i64 [[SUB]])
@@ -313,7 +313,7 @@ define i64 @sub_single_use_in_unreachable_block(i64 %a, i64 %b) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
 ; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
-; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[A]], [[B]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
 ; CHECK-NEXT:    ret i64 0
 ; CHECK:       [[UNREACHABLE_BB:.*:]]
 ; CHECK-NEXT:    call void @use(i64 [[SUB]])
@@ -337,7 +337,7 @@ define i64 @sub_uses_in_two_unreachable_blocks(i64 %a, i64 %b) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
 ; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
-; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[A]], [[B]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
 ; CHECK-NEXT:    ret i64 0
 ; CHECK:       [[UNREACHABLE_BB_1:.*:]]
 ; CHECK-NEXT:    call void @use(i64 [[SUB]])
@@ -365,7 +365,7 @@ define i64 @sub_use_in_unreachable_block_ignored(i64 %a, i64 %b) {
 ; CHECK-LABEL: define i64 @sub_use_in_unreachable_block_ignored(
 ; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[A]], [[B]]
 ; CHECK-NEXT:    br label %[[MID:.*]]
 ; CHECK:       [[MID]]:
 ; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]

>From bf39372ea2c16741ed090e435ad82decb24099e6 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Mon, 7 Sep 2026 14:57:35 +0100
Subject: [PATCH 3/3] !fixup return I

---
 llvm/lib/Transforms/Scalar/ConstraintElimination.cpp | 12 +++++++-----
 .../strengthen-flags-at-uses.ll                      |  6 +++---
 2 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index b7e8b22cc6a94..dcaf1d3e48da8 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -76,21 +76,23 @@ static Instruction *getContextInstForUse(Use &U) {
 }
 
 /// Returns the closest program point dominating all uses of \p I, or nullptr if
-/// \p I has no uses or all of them are in unreachable blocks.
+/// \p I has no uses.
 static Instruction *findCommonDominatorOfUses(Instruction &I,
                                               DominatorTree &DT) {
   Instruction *CommonDom = nullptr;
   unsigned NumUses = 0;
   for (Use &U : I.uses()) {
-    ++NumUses;
-    if (NumUses == 16)
-      return nullptr;
+    // Conservatively use original instruction, if there are too many uses.
+    if (++NumUses == 16)
+      return &I;
     Instruction *UserI = getContextInstForUse(U);
     CommonDom =
         CommonDom ? DT.findNearestCommonDominator(CommonDom, UserI) : UserI;
   }
+  if (!CommonDom)
+    return &I;
   // Uses in unreachable blocks are not in the dominator tree.
-  return CommonDom && DT.getNode(CommonDom->getParent()) ? CommonDom : nullptr;
+  return DT.getNode(CommonDom->getParent()) ? CommonDom : &I;
 }
 
 namespace {
diff --git a/llvm/test/Transforms/ConstraintElimination/strengthen-flags-at-uses.ll b/llvm/test/Transforms/ConstraintElimination/strengthen-flags-at-uses.ll
index afbcf2b039d61..8929a35c3fa0d 100644
--- a/llvm/test/Transforms/ConstraintElimination/strengthen-flags-at-uses.ll
+++ b/llvm/test/Transforms/ConstraintElimination/strengthen-flags-at-uses.ll
@@ -259,7 +259,7 @@ define void @sub_16_uses_fact_at_def(i64 %a, i64 %b) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
 ; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
-; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[A]], [[B]]
 ; CHECK-NEXT:    br label %[[USE_BB:.*]]
 ; CHECK:       [[USE_BB]]:
 ; CHECK-NEXT:    call void @use(i64 [[SUB]])
@@ -313,7 +313,7 @@ define i64 @sub_single_use_in_unreachable_block(i64 %a, i64 %b) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
 ; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
-; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[A]], [[B]]
 ; CHECK-NEXT:    ret i64 0
 ; CHECK:       [[UNREACHABLE_BB:.*:]]
 ; CHECK-NEXT:    call void @use(i64 [[SUB]])
@@ -337,7 +337,7 @@ define i64 @sub_uses_in_two_unreachable_blocks(i64 %a, i64 %b) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[C:%.*]] = icmp uge i64 [[A]], [[B]]
 ; CHECK-NEXT:    call void @llvm.assume(i1 [[C]])
-; CHECK-NEXT:    [[SUB:%.*]] = sub i64 [[A]], [[B]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[A]], [[B]]
 ; CHECK-NEXT:    ret i64 0
 ; CHECK:       [[UNREACHABLE_BB_1:.*:]]
 ; CHECK-NEXT:    call void @use(i64 [[SUB]])



More information about the llvm-commits mailing list