Skip to content
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

[KnownBits] Remove hasConflict() assertions #94568

Merged
merged 16 commits into from
Jun 7, 2024
Merged

[KnownBits] Remove hasConflict() assertions #94568

merged 16 commits into from
Jun 7, 2024

Conversation

c8ef
Copy link
Contributor

@c8ef c8ef commented Jun 6, 2024

close: #94436

@llvmbot
Copy link
Member

llvmbot commented Jun 6, 2024

@llvm/pr-subscribers-llvm-selectiondag
@llvm/pr-subscribers-llvm-analysis
@llvm/pr-subscribers-llvm-ir

@llvm/pr-subscribers-llvm-support

Author: None (c8ef)

Changes

close: #94436


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

1 Files Affected:

  • (modified) llvm/include/llvm/Support/KnownBits.h (+2-9)
diff --git a/llvm/include/llvm/Support/KnownBits.h b/llvm/include/llvm/Support/KnownBits.h
index ba4a5f01036ca..c206bd77d5880 100644
--- a/llvm/include/llvm/Support/KnownBits.h
+++ b/llvm/include/llvm/Support/KnownBits.h
@@ -48,7 +48,6 @@ struct KnownBits {
 
   /// Returns true if we know the value of all bits.
   bool isConstant() const {
-    assert(!hasConflict() && "KnownBits conflict!");
     return Zero.popcount() + One.popcount() == getBitWidth();
   }
 
@@ -74,16 +73,10 @@ struct KnownBits {
   }
 
   /// Returns true if value is all zero.
-  bool isZero() const {
-    assert(!hasConflict() && "KnownBits conflict!");
-    return Zero.isAllOnes();
-  }
+  bool isZero() const { return Zero.isAllOnes(); }
 
   /// Returns true if value is all one bits.
-  bool isAllOnes() const {
-    assert(!hasConflict() && "KnownBits conflict!");
-    return One.isAllOnes();
-  }
+  bool isAllOnes() const { return One.isAllOnes(); }
 
   /// Make all bits known to be zero and discard any previous information.
   void setAllZero() {

@nikic
Copy link
Contributor

nikic commented Jun 6, 2024

These assertions are not just in KnownBits.h, but also spread across many other places. Basically do a git grep "hasConflict()" (There are currently 88 occurrences, though not all of them are in assertions.)

@c8ef
Copy link
Contributor Author

c8ef commented Jun 6, 2024

These assertions are not just in KnownBits.h, but also spread across many other places. Basically do a git grep "hasConflict()" (There are currently 88 occurrences, though not all of them are in assertions.)

I removed more assertions in other files.

@nikic
Copy link
Contributor

nikic commented Jun 6, 2024

There are more checks in KnownBitsTest, but I guess we can drop those in a second step, when removing the various if (!hasConflict()) setAllZero(); bits.

@nikic
Copy link
Contributor

nikic commented Jun 6, 2024

Though one testing-related change we should do is drop this check:

if (Known.hasConflict())
This means the KnownBits implementation will actually get tested with conflict values, so we can be sure this doesn't produce unexpected crashes somewhere.

@c8ef
Copy link
Contributor Author

c8ef commented Jun 6, 2024

Though one testing-related change we should do is drop this check:

if (Known.hasConflict())

This means the KnownBits implementation will actually get tested with conflict values, so we can be sure this doesn't produce unexpected crashes somewhere.

After removing this statement, I immediately encounter an error locally. Is this the expected behavior? Since the purpose of this PR is to enable a bit to be both zero and one simultaneously, perhaps we should also remove this assert?

[==========] Running 16 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 16 tests from KnownBitsTest
[ RUN      ] KnownBitsTest.AddCarryExhaustive
Assertion failed: !(CarryZero && CarryOne) && "Carry can't be zero and one at the same time", file .\llvm-project\llvm\lib\Support\KnownBits.cpp, line 25

UPD:

If this assertion is also removed, there will be numerous failures such as:

llvm-project\llvm\unittests\Support\KnownBitsTest.cpp(138): error: Expected equality of these values:
  Exact
    Which is: !!!!
  Computed
    Which is: ????

@jayfoad
Copy link
Contributor

jayfoad commented Jun 6, 2024

I expect you would have to make some changes like this, since even the supposedly optimal KnownBits implementations do not always report conflict when they should:

diff --git a/llvm/unittests/Support/KnownBitsTest.cpp b/llvm/unittests/Support/KnownBitsTest.cpp
index 824cf7501fd4..acec5dbfb4e6 100644
--- a/llvm/unittests/Support/KnownBitsTest.cpp
+++ b/llvm/unittests/Support/KnownBitsTest.cpp
@@ -95,9 +95,10 @@ static void testBinaryOpExhaustive(StringRef Name, BinaryBitsFn BitsFn,
           });
         });
 
-        EXPECT_TRUE(!Computed.hasConflict());
-        EXPECT_TRUE(checkResult(Name, Exact, Computed, {Known1, Known2},
-                                CheckOptimality));
+        if (!Exact.hasConflict()) {
+          EXPECT_TRUE(checkResult(Name, Exact, Computed, {Known1, Known2},
+                                  CheckOptimality));
+        }
         // In some cases we choose to return zero if the result is always
         // poison.
         if (RefinePoisonToZero && Exact.hasConflict()) {

@c8ef
Copy link
Contributor Author

c8ef commented Jun 6, 2024

static KnownBits computeForAddCarry(
    const KnownBits &LHS, const KnownBits &RHS,
    bool CarryZero, bool CarryOne) {
  APInt PossibleSumZero = LHS.getMaxValue() + RHS.getMaxValue() + !CarryZero;
  APInt PossibleSumOne = LHS.getMinValue() + RHS.getMinValue() + CarryOne;

  // Compute known bits of the carry.
  APInt CarryKnownZero = ~(PossibleSumZero ^ LHS.Zero ^ RHS.Zero);
  APInt CarryKnownOne = PossibleSumOne ^ LHS.One ^ RHS.One;

  // Compute set of known bits (where all three relevant bits are known).
  APInt LHSKnownUnion = LHS.Zero | LHS.One;
  APInt RHSKnownUnion = RHS.Zero | RHS.One;
  APInt CarryKnownUnion = std::move(CarryKnownZero) | CarryKnownOne;
  APInt Known = std::move(LHSKnownUnion) & RHSKnownUnion & CarryKnownUnion;

  // vvvv This assert also hit in test vvvv
  assert((PossibleSumZero & Known) == (PossibleSumOne & Known) &&
         "known bits of sum differ");
  // ^^^^

  // Compute known bits of the result.
  KnownBits KnownOut;
  KnownOut.Zero = ~std::move(PossibleSumZero) & Known;
  KnownOut.One = std::move(PossibleSumOne) & Known;
  return KnownOut;
}

@nikic
Copy link
Contributor

nikic commented Jun 6, 2024

@c8ef Yeah, you should drop that assert.

@c8ef
Copy link
Contributor Author

c8ef commented Jun 6, 2024

Though one testing-related change we should do is drop this check:

if (Known.hasConflict())

This means the KnownBits implementation will actually get tested with conflict values, so we can be sure this doesn't produce unexpected crashes somewhere.

If we remove this check, it appears that many tests are failing. Currently, I am skipping values that have conflicts, but I am unsure if this is the correct approach. Please inform me if there are any issues.

@c8ef c8ef requested a review from nikic June 6, 2024 13:05
@@ -4212,7 +4212,6 @@ KnownBits SelectionDAG::computeKnownBits(SDValue Op, const APInt &DemandedElts,
break;
}

assert(!Known.hasConflict() && "Bits known to be one AND zero?");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe just focus on the middle-end initially and then look at removing the DAG/GlobalISel assertions later?

Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like it's safer to get rid of it everyone at once.

@@ -726,6 +741,9 @@ TEST(KnownBitsTest, SExtInReg) {
unsigned Bits = 4;
for (unsigned FromBits = 1; FromBits <= Bits; ++FromBits) {
ForeachKnownBits(Bits, [&](const KnownBits &Known) {
if (Known.hasConflict())
return;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be slightly better to move this check until after the Known.sextInReg() call (and also something similar for the other tests). Basically still call the KnownBits helper to make sure it doesn't assert, but don't check what the result will be, as it could be anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@nikic
Copy link
Contributor

nikic commented Jun 6, 2024

There are some test failures in pre-commit CI:

  LLVM :: Analysis/ScalarEvolution/ashr.ll
  LLVM :: Analysis/ScalarEvolution/pr76234.ll
  LLVM :: CodeGen/AArch64/GlobalISel/form-bitfield-extract-from-sextinreg.mir
  LLVM :: CodeGen/AMDGPU/ds-sub-offset.ll
  LLVM :: Transforms/InstCombine/select-obo-peo-ops.ll
  LLVM :: Transforms/LoopStrengthReduce/lsr-term-fold-negative-testcase.ll
  LLVM-Unit :: Analysis/./AnalysisTests.exe/ValueTrackingTest/ComputeNumSignBits_PR32045
  LLVM-Unit :: IR/./IRTests.exe/DemandedBitsTest/Add
  LLVM-Unit :: IR/./IRTests.exe/DemandedBitsTest/Sub

I think most of these are due to the various if (!Known.hasConflict()) Known.setAllZero(); check that you dropped. You could either adjust the test expectations, or leave dropping these checks for a later patch, so that this one can be NFC (no functional change).

Copy link

github-actions bot commented Jun 6, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@jayfoad
Copy link
Contributor

jayfoad commented Jun 6, 2024

or leave dropping these checks for a later patch, so that this one can be NFC

I would prefer making this an NFC patch that just drops assertions.

@c8ef c8ef requested a review from nikic June 7, 2024 06:36
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.

This looks mostly good to me, but I think some of the setAllZeros() removals should be undone.

// Result is either known Zero or UB. Return Zero either way.
// Checking this earlier saves us a lot of special cases later on.
Known.setAllZero();
// Result is either known Zero or UB.
Copy link
Contributor

Choose a reason for hiding this comment

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

This setAllZero() call should be retained. Otherwise we would now provide a worse result if the LHS is zero. (It would go from result zero to result unknown).

@@ -403,8 +391,7 @@ KnownBits KnownBits::lshr(const KnownBits &LHS, const KnownBits &RHS,
if (Exact) {
unsigned FirstOne = LHS.countMaxTrailingZeros();
if (FirstOne < MinShiftAmount) {
// Always poison. Return zero because we don't like returning conflict.
Known.setAllZero();
// Always poison.
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar here and in the ashr implementation. We go from returning zero to returning unknown, which is responsible for the regressions in SCEV tests where we go from range [0, 1) to full-set.

The correct replacement would be something like setConflict(), but given that we don't have that yet and wouldn't be able to make use of it for folding I think it's best to leave these cases alone.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It appears that in the current version(
cfe2878), the testcase llvm/test/Analysis/ScalarEvolution/pr76234.ll is still experiencing a regression from [0, 1) to full-set. Is this the intended behavior?

�_bk;t=1717760175285�# | <stdin>:4:2: note: possible intended match here
�_bk;t=1717760175285�# |  --> %B9 U: full-set S: full-set
�_bk;t=1717760175285�# |  ^
�_bk;t=1717760175285�# | 
�_bk;t=1717760175285�# | Input file: <stdin>
�_bk;t=1717760175285�# | Check file: C:\ws\src\llvm\test\Analysis\ScalarEvolution\pr76234.ll
�_bk;t=1717760175285�# | 
�_bk;t=1717760175285�# | -dump-input=help explains the following input dump.
�_bk;t=1717760175285�# | 
�_bk;t=1717760175285�# | Input was:
�_bk;t=1717760175285�# | <<<<<<
�_bk;t=1717760175285�# |           1: Printing analysis 'Scalar Evolution Analysis' for function 'PR76234': 
�_bk;t=1717760175285�# |           2: Classifying expressions for: @PR76234 
�_bk;t=1717760175285�# |           3:  %B9 = shl i896 0, -264147265567832623176169892458258303259423663018060761063980354513336951278362429737208627943828593947337197496628564339441173779751342768625269489231469788454193341999502542084365758838213220526512116454105594202074014146375780869419198449383518238244769290448868999168 
�_bk;t=1717760175285�# | next:9'0                                                                                                                                                                                                                                                                                                       X error: no match found
�_bk;t=1717760175285�# |           4:  --> %B9 U: full-set S: full-set 
�_bk;t=1717760175285�# | next:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
�_bk;t=1717760175285�# | next:9'1      ?                                possible intended match
�_bk;t=1717760175285�# |           5:  %B39 = ashr i896 %B9, 1 
�_bk;t=1717760175285�# | next:9'0     ~~~~~~~~~~~~~~~~~~~~~~~~~

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, I see. In that case I think it would be best to just revert all the setAllZero() related changes. We will have to update consuming code first so it can do something useful with conflict values before we start generating them.

@@ -448,8 +433,7 @@ KnownBits KnownBits::ashr(const KnownBits &LHS, const KnownBits &RHS,
MinShiftAmount = 1;
if (LHS.isUnknown()) {
if (MinShiftAmount == BitWidth) {
// Always poison. Return zero because we don't like returning conflict.
Known.setAllZero();
// Always poison.
Copy link
Contributor

Choose a reason for hiding this comment

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

This one, and the next one as well.

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.

LGTM

@@ -38,8 +38,10 @@ static void TestBinOpExhaustive(Fn1 PropagateFn, Fn2 EvalFn) {

APInt AB1R = PropagateFn(0, AOut, Known1Redacted, Known2Redacted);
APInt AB2R = PropagateFn(1, AOut, Known1Redacted, Known2Redacted);
EXPECT_EQ(AB1, AB1R);
EXPECT_EQ(AB2, AB2R);
if (!Known1Redacted.hasConflict() && !Known2Redacted.hasConflict()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this change still needed in this version of the patch?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Confirm it is still needed.

});
}
}

static void testBinaryOpExhaustive(StringRef Name, BinaryBitsFn BitsFn,
BinaryIntFn IntFn,
bool CheckOptimality = true,
bool RefinePoisonToZero = false) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should restore this RefinePoisonToZero stuff, since you have restored all the "if conflict return zero" cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will handle it in another pr.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nikic nikic merged commit b25b1db into llvm:main Jun 7, 2024
5 of 6 checks passed
@@ -653,8 +656,10 @@ TEST(KnownBitsTest, GetMinMaxVal) {
Min = APIntOps::umin(Min, N);
Max = APIntOps::umax(Max, N);
});
EXPECT_EQ(Min, Known.getMinValue());
EXPECT_EQ(Max, Known.getMaxValue());
if (!Known.hasConflict()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If there's a conflict you could immediately "continue" at the top of the loop to save some time. Same in the next three functions below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

But in this case you are not calling Known.anything() before checking for conflict and bailing out!

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, yes, I guess we should store Known.getMinValue() etc in a variable first, before the check. Or do people think there is not much value in checking all the functions with conflict inputs?

Copy link
Contributor

Choose a reason for hiding this comment

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

Or do people think there is not much value in checking all the functions with conflict inputs?

I'm not sure how much value there is right now.

At some point we should check that KnownBits functions return conflict when they ought to, and part of that would definitely involve testing them on conflict inputs.

Copy link
Contributor

Choose a reason for hiding this comment

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

These particular APIs don't return KnownBits, so they don't really have any specific behavior they should exhibit for conflict inputs beyond "don't assert".

@@ -752,8 +765,10 @@ TEST(KnownBitsTest, CommonBitsSet) {
HasCommonBitsSet |= N1.intersects(N2);
});
});
EXPECT_EQ(!HasCommonBitsSet,
KnownBits::haveNoCommonBitsSet(Known1, Known2));
if (!Known1.hasConflict() && !Known2.hasConflict()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe this is necessary because we permit conflicts in test generation. Therefore, even if we revert the set to zero, these tests will only pass when there are no conflicts.

Copy link
Contributor

Choose a reason for hiding this comment

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

I meant that you could save some time here by testing for conflict as the first thing in the loop body, before the nested ForeachNumInKnownBits loops.

@c8ef c8ef deleted the KnownBits branch June 7, 2024 15:07
@HerrCai0907 HerrCai0907 mentioned this pull request Jun 13, 2024
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.

[KnownBits] Remove hasConflict() assertions
5 participants