Skip to content

[ValueTracking] Handle not cond to assume. #127140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

andjo403
Copy link
Contributor

@andjo403 andjo403 commented Feb 13, 2025

Addresses this comment #126423 (comment).
Also solves the ephemeral issue from #118406 for known bits of x in assume (not trunc x to i1)

Proof: https://alive2.llvm.org/ce/z/Bkg2f6

@llvmbot
Copy link
Member

llvmbot commented Feb 13, 2025

@llvm/pr-subscribers-llvm-transforms

@llvm/pr-subscribers-llvm-analysis

Author: Andreas Jonson (andjo403)

Changes

Addresses this comment #126423 (comment).
Also solves the ephemeral issue from #118406 for known bits of x in assume (not trunc x to i1)

is it enough with the test in https://github.com/llvm/llvm-project/blob/3d140004c70e2bc79416825e43207e8b711c56d9/llvm/test/Transforms/GVN/assume.ll ?


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

1 Files Affected:

  • (modified) llvm/lib/Analysis/ValueTracking.cpp (+22-16)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 28d7e1ce401e4..1ac37603172a1 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -441,9 +441,6 @@ void llvm::computeKnownBitsFromRangeMetadata(const MDNode &Ranges,
 }
 
 static bool isEphemeralValueOf(const Instruction *I, const Value *E) {
-  SmallVector<const Value *, 16> WorkSet(1, I);
-  SmallPtrSet<const Value *, 32> Visited;
-  SmallPtrSet<const Value *, 16> EphValues;
 
   // The instruction defining an assumption's condition itself is always
   // considered ephemeral to that assumption (even if it has other
@@ -451,6 +448,21 @@ static bool isEphemeralValueOf(const Instruction *I, const Value *E) {
   if (is_contained(I->operands(), E))
     return true;
 
+  SmallPtrSet<const Value *, 32> Visited{I};
+  SmallPtrSet<const Value *, 16> EphValues{I};
+
+  Value *X;
+  if (match(I, m_Intrinsic<Intrinsic::assume>(m_Not(m_Value(X))))) {
+    if (X == E)
+      return true;
+    auto *Not = cast<Instruction>(I->getOperand(0));
+    Visited.insert(Not);
+    EphValues.insert(Not);
+    I = Not;
+  }
+
+  SmallVector<const Value *, 16> WorkSet(I->operands());
+
   while (!WorkSet.empty()) {
     const Value *V = WorkSet.pop_back_val();
     if (!Visited.insert(V).second)
@@ -463,13 +475,11 @@ static bool isEphemeralValueOf(const Instruction *I, const Value *E) {
       if (V == E)
         return true;
 
-      if (V == I || (isa<Instruction>(V) &&
-                     !cast<Instruction>(V)->mayHaveSideEffects() &&
-                     !cast<Instruction>(V)->isTerminator())) {
-       EphValues.insert(V);
-       if (const User *U = dyn_cast<User>(V))
-         append_range(WorkSet, U->operands());
-      }
+      if (const auto *II = dyn_cast<Instruction>(V))
+        if (!II->mayHaveSideEffects() && !II->isTerminator()) {
+          EphValues.insert(V);
+          append_range(WorkSet, II->operands());
+        }
     }
   }
 
@@ -10214,11 +10224,8 @@ void llvm::findValuesAffectedByCondition(
     CmpPredicate Pred;
     Value *A, *B, *X;
 
-    if (IsAssume) {
+    if (IsAssume)
       AddAffected(V);
-      if (match(V, m_Not(m_Value(X))))
-        AddAffected(X);
-    }
 
     if (match(V, m_LogicalOp(m_Value(A), m_Value(B)))) {
       // assume(A && B) is split to -> assume(A); assume(B);
@@ -10302,8 +10309,7 @@ void llvm::findValuesAffectedByCondition(
       // Assume is checked here as X is already added above for assumes in
       // addValueAffectedByCondition
       AddAffected(X);
-    } else if (!IsAssume && match(V, m_Not(m_Value(X)))) {
-      // Assume is checked here to avoid issues with ephemeral values
+    } else if (match(V, m_Not(m_Value(X)))) {
       Worklist.push_back(X);
     }
   }

@andjo403 andjo403 changed the title [ValueTracking] Handle not in assume argument in isEphemeralValueOf. [ValueTracking] Handle not cond to assume. Feb 15, 2025
@andjo403
Copy link
Contributor Author

Added test and a proof.

SmallPtrSet<const Value *, 16> EphValues{I};

Value *X;
if (match(I, m_Intrinsic<Intrinsic::assume>(m_Not(m_Value(X))))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am ok with the IR diff. But I am a bit confused by this line. Do we really need to specially handle assume(not(cond))?

That is, we should also match trunc here if we want to remove !IsAssume && from the following code:

} else if (!IsAssume && match(V, m_Trunc(m_Value(X)))) {

Copy link
Contributor Author

@andjo403 andjo403 Feb 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the !IsAssume && for the trunc check is only to avoid to add the same value twice to the AssumptionCache as it is added by the

if (match(I, m_CombineOr(m_PtrToInt(m_Value(Op)), m_Trunc(m_Value(Op))))) {
if (isa<Instruction>(Op) || isa<Argument>(Op))
InsertAffected(Op);
}

via
if (IsAssume) {
AddAffected(V);

This was the only way that I was able to think of how to solve supporting not in assumes.

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like special-casing the not here.

What would happen if we treat any instruction that feeds into the assume as ephemeral, even if it has other users?

@andjo403
Copy link
Contributor Author

andjo403 commented Feb 15, 2025

What would happen if we treat any instruction that feeds into the assume as ephemeral, even if it has other users?

I maybe missunderstad your question but is that not already done

// The instruction defining an assumption's condition itself is always
// considered ephemeral to that assumption (even if it has other
// non-ephemeral users). See r246696's test case for an example.
if (is_contained(I->operands(), E))
return true;

the problem is that it only is the argument to the assume function that ignore other users not all instructions that builds up the condition.
added handling for the not as that is the only logical operation that is add to the AssumptionCache as and/or is split up to multiple assumes before added.

@nikic
Copy link
Contributor

nikic commented Feb 15, 2025

I maybe missunderstad your question but is that not already done

I'm suggesting to do that not just for direct assume arguments, but for all instructions. I assume this will have some kind of negative fallout, but I'd like to understand what it is.

@andjo403
Copy link
Contributor Author

I'm suggesting to do that not just for direct assume arguments, but for all instructions. I assume this will have some kind of negative fallout, but I'd like to understand what it is.

As I understand it isEphemeralValueOf will effectively always return true in that case as it is only called when the value E is before the assume and all instructions that builds up the condition to the assume will be ephemeral.

maybe an other way to make isEphemeralValueOf is to use findValuesAffectedByCondition function to make the ephemeral check like this:

  SmallVector<const Value *, 8> Affected;
  auto InsertAffected = [&Affected](Value *V) { Affected.push_back(V); };
  findValuesAffectedByCondition(Assume->getArgOperand(0), /*IsAssume=*/true,
                                InsertAffected);

  return is_contained(Affected, E) &&
         llvm::all_of(E->users(),
                      [&](const User *U) { return is_contained(Affected, U); });

avoids the need for special handling of not

@andjo403
Copy link
Contributor Author

andjo403 commented Feb 16, 2025

But my proposal to use findValuesAffectedByCondition also is no good as it removes the assume in

define void @pr47496(i8 %x) {
; CHECK-LABEL: @pr47496(
; CHECK-NEXT: [[CMP:%.*]] = icmp slt i8 [[X:%.*]], 0
; CHECK-NEXT: [[XOR:%.*]] = xor i1 [[CMP]], true
; CHECK-NEXT: call void @llvm.assume(i1 [[XOR]])
; CHECK-NEXT: call void @use(i1 false)
; CHECK-NEXT: ret void
;
%cmp = icmp slt i8 %x, 0
%xor = xor i1 %cmp, true
call void @llvm.assume(i1 %xor)
call void @use(i1 %cmp)
ret void
}
as the %cmp is not ephemeral as there is a user that is not ephemeral

@nikic
Copy link
Contributor

nikic commented Feb 21, 2025

See #128152 for another ephemeral value problem. This is why I think special casing not is not a solution.

I briefly tried the variant where we don't consider extra uses at nikic@37f1d40 / dtcxzyw/llvm-opt-benchmark#2135. That would need more work, but I think that's the general direction we should go to resolve this more thoroughly.

@andjo403 andjo403 closed this Feb 21, 2025
@andjo403 andjo403 deleted the assumeNot branch February 21, 2025 16:01
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.

4 participants