Skip to content

[ConstraintElimination] Defer removal of simplified ssub.with.overflow - #215135

Open
fhahn wants to merge 1 commit into
llvm:mainfrom
fhahn:ce-ssub-removal
Open

[ConstraintElimination] Defer removal of simplified ssub.with.overflow#215135
fhahn wants to merge 1 commit into
llvm:mainfrom
fhahn:ce-ssub-removal

Conversation

@fhahn

@fhahn fhahn commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

replaceSubOverflowUses erased the intrinsic as soon as it became dead. That frees the intrinsic's operand Use array, but the worklist can still hold UseCheck entries pointing into it, storing a now invalid pointer to a Use *.

Instead of erasing the intrinsic in place, poison its arguments and push it onto ToRemove.

@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-llvm-transforms

Author: Florian Hahn (fhahn)

Changes

replaceSubOverflowUses erased the intrinsic as soon as it became dead. That frees the intrinsic's operand Use array, but the worklist can still hold UseCheck entries pointing into it, storing a now invalid pointer to a Use *.

Instead of erasing the intrinsic in place, poison its arguments and push it onto ToRemove.


Full diff: https://github.com/llvm/llvm-project/pull/215135.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/Scalar/ConstraintElimination.cpp (+7-1)
  • (modified) llvm/test/Transforms/ConstraintElimination/ssub-with-overflow.ll (+46)
diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index b31ae075e8f4a..3feb9eceec8b5 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -1950,7 +1950,13 @@ static bool replaceSubOverflowUses(IntrinsicInst *II, Value *A, Value *B,
   }
 
   if (II->use_empty()) {
-    II->eraseFromParent();
+    // Do not erase II here: the worklist may still hold Uses of II's operands,
+    // and evaluating those entries after II has been freed reads freed memory.
+    // Poison the operands to invalidate such entries and defer the removal,
+    // like for the dead extractvalue users above.
+    for (Use &Arg : II->args())
+      Arg.set(PoisonValue::get(Arg->getType()));
+    ToRemove.push_back(II);
     Changed = true;
   }
   return Changed;
diff --git a/llvm/test/Transforms/ConstraintElimination/ssub-with-overflow.ll b/llvm/test/Transforms/ConstraintElimination/ssub-with-overflow.ll
index 4a270881bebf8..9077c3647589d 100644
--- a/llvm/test/Transforms/ConstraintElimination/ssub-with-overflow.ll
+++ b/llvm/test/Transforms/ConstraintElimination/ssub-with-overflow.ll
@@ -2,6 +2,7 @@
 ; RUN: opt -passes=constraint-elimination -S %s | FileCheck %s
 
 declare { i8, i1 } @llvm.ssub.with.overflow.i8(i8, i8)
+declare { i1, i1 } @llvm.ssub.with.overflow.i1(i1, i1)
 
 define i8 @ssub_no_overflow_due_to_or_conds(i8 %a, i8 %b) {
 ; CHECK-LABEL: @ssub_no_overflow_due_to_or_conds(
@@ -349,3 +350,48 @@ exit.ok:
 exit.fail:
   ret i8 0
 }
+
+; The arguments of the intrinsic are compares that are themselves checked, and
+; the block holding the intrinsic comes before the block defining them.
+define i1 @ssub_simplified_before_uses_of_arguments(i32 %a) {
+; CHECK-LABEL: @ssub_simplified_before_uses_of_arguments(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[CHECK_1:%.*]]
+; CHECK:       math:
+; CHECK-NEXT:    [[TMP0:%.*]] = sub nsw i1 [[C:%.*]], [[D:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = or i1 [[TMP0]], false
+; CHECK-NEXT:    ret i1 [[R]]
+; CHECK:       check.1:
+; CHECK-NEXT:    [[C]] = icmp sgt i32 [[A:%.*]], 0
+; CHECK-NEXT:    [[D]] = icmp sgt i32 [[A]], 1
+; CHECK-NEXT:    [[C_1:%.*]] = icmp sge i1 [[C]], [[D]]
+; CHECK-NEXT:    br i1 [[C_1]], label [[CHECK_2:%.*]], label [[EXIT:%.*]]
+; CHECK:       check.2:
+; CHECK-NEXT:    [[C_2:%.*]] = icmp sge i1 [[D]], false
+; CHECK-NEXT:    br i1 [[C_2]], label [[MATH:%.*]], label [[EXIT]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i1 false
+;
+entry:
+  br label %check.1
+
+math:
+  %op = call { i1, i1 } @llvm.ssub.with.overflow.i1(i1 %c, i1 %d)
+  %res = extractvalue { i1, i1 } %op, 0
+  %status = extractvalue { i1, i1 } %op, 1
+  %r = or i1 %res, %status
+  ret i1 %r
+
+check.1:
+  %c = icmp sgt i32 %a, 0
+  %d = icmp sgt i32 %a, 1
+  %c.1 = icmp sge i1 %c, %d
+  br i1 %c.1, label %check.2, label %exit
+
+check.2:
+  %c.2 = icmp sge i1 %d, 0
+  br i1 %c.2, label %math, label %exit
+
+exit:
+  ret i1 false
+}

replaceSubOverflowUses erased the intrinsic as soon as it became dead.
That frees the intrinsic's operand Use array, but the worklist can still
hold UseCheck entries pointing into it, storing a now invalid pointer to
a Use *.

Instead of erasing the intrinsic in place, poison its arguments and push
it onto ToRemove.
@fhahn
fhahn force-pushed the ce-ssub-removal branch from 6f19185 to 54b025c Compare August 9, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant