This is an archive of the discontinued LLVM Phabricator instance.

[KnownBits] Improve implementation of `KnownBits::abs`
ClosedPublic

Authored by goldstein.w.n on May 7 2023, 11:58 PM.

Details

Summary

abs preserves the lowest set bit, so if we know the lowest set bit,
set it in the output.

As well, implement the case where the operand is known negative.

Diff Detail

Event Timeline

goldstein.w.n created this revision.May 7 2023, 11:58 PM
Herald added a project: Restricted Project. · View Herald TranscriptMay 7 2023, 11:58 PM
goldstein.w.n requested review of this revision.May 7 2023, 11:58 PM
Herald added a project: Restricted Project. · View Herald TranscriptMay 7 2023, 11:58 PM
RKSimon added inline comments.May 8 2023, 3:29 AM
llvm/lib/Support/KnownBits.cpp
417

Maybe call these MaxTZ/MinTZ? Just to make it clear later on we're not talking about the possible min/max values

465

Worth adding a assert(!KnownAbs.hasConflict() && "KnownBits conflict!"); ?

nikic added inline comments.May 8 2023, 3:44 AM
llvm/lib/Support/KnownBits.cpp
470

You can pass IntMinIsPoison to the NSW argument.

473

Under which circumstance is KnownAbs better? Seems fishy.

goldstein.w.n added inline comments.May 8 2023, 5:20 PM
llvm/lib/Support/KnownBits.cpp
470

You can pass IntMinIsPoison to the NSW argument.

Can we? The 0-Negative_X is positive unless Negative_X == INT_MIN so if IntMinIsPoison we can't set NSW. Also would do slightly better and just do KnownAbs.isNonNegative() because it uses the IntMinIsPoison condition (and others) to try and set output sign.

But either way, since KnownAbs already has logic for setting output sign and we combine our result from negation with KnownAbs its kind of a non-issue.

473

Under which circumstance is KnownAbs better? Seems fishy.

The output sign at the very least. But when testing (even when I was getting the right sign from the computeForAddSub assert(KnownAbs.Zero.isSubsetOf(KnownNeg.Zero) && KnownAbs.isSubsetOf(KnownNeg.One)) was failing on the exhaustive tests.

goldstein.w.n marked 3 inline comments as done.May 8 2023, 8:00 PM
goldstein.w.n added inline comments.
llvm/lib/Support/KnownBits.cpp
473

Its just the sign bit, ill update comment.

Update comment about mergeing result. Check inputs/outputs

nikic added inline comments.May 9 2023, 4:06 AM
llvm/lib/Support/KnownBits.cpp
470

I don't understand. The only value for which 0-X wraps is INT_MIN, which is exactly what the IntMinIsPoison flag controls. The entire purpose of that flag is to allow an nsw assumption on the negation.

goldstein.w.n added inline comments.May 13 2023, 10:51 AM
llvm/lib/Support/KnownBits.cpp
470

Oh I mistakenly though something like sub nsw i8 0, 129 would be poison but guess not.
Either way though, we 'join' with the original abs value to get the signbit so nsw is unneeded no?

nikic added inline comments.May 13 2023, 11:35 AM
llvm/lib/Support/KnownBits.cpp
470

But can we drop that join if NSW is passed? Just making this if (isNegative(X)) return neg(X); would make this logic a lot cleaner.

goldstein.w.n added inline comments.May 13 2023, 12:52 PM
llvm/lib/Support/KnownBits.cpp
470

But can we drop that join if NSW is passed? Just making this if (isNegative(X)) return neg(X); would make this logic a lot cleaner.

I doesn't seem to cover all cases:

KnownBits Zero(getBitWidth());
Zero.setAllZero();

KnownBits KnownNeg = computeForAddSub(
    /*Add*/ false, KnownAbs.isNegative(), Zero, *this);

// Preserve signbit from `KnownAbs` as it has additional logic for figuring
// it out that we don't want to duplicate here.
KnownBits KnownAbsNoSign = KnownAbs;
KnownBits KnownNegNoSign = KnownNeg;
KnownAbsNoSign.One.clearSignBit();
KnownAbsNoSign.Zero.clearSignBit();
KnownNegNoSign.One.clearSignBit();
KnownNegNoSign.Zero.clearSignBit();

assert(KnownAbsNoSign.One.isSubsetOf(KnownNegNoSign.One));
assert(KnownAbsNoSign.Zero.isSubsetOf(KnownNegNoSign.Zero));

assert(KnownAbs.One.isSubsetOf(KnownNeg.One));
assert(KnownAbs.Zero.isSubsetOf(KnownNeg.Zero));

KnownNeg.One.clearSignBit();
KnownNeg.Zero.clearSignBit();
KnownAbs.One |= KnownNeg.One;
KnownAbs.Zero |= KnownNeg.Zero;

On the exhaustive tests fails:

KnownBits.cpp:440: llvm::KnownBits llvm::KnownBits::abs(bool) const: Assertion `KnownAbs.Zero.isSubsetOf(KnownNeg.Zero)' failed.
goldstein.w.n added inline comments.May 13 2023, 12:56 PM
llvm/lib/Support/KnownBits.cpp
470

But can we drop that join if NSW is passed? Just making this if (isNegative(X)) return neg(X); would make this logic a lot cleaner.

I doesn't seem to cover all cases:

KnownBits Zero(getBitWidth());
Zero.setAllZero();

KnownBits KnownNeg = computeForAddSub(
    /*Add*/ false, KnownAbs.isNegative(), Zero, *this);

This should be isNonNegative() but still fails the assertion.

// Preserve signbit from `KnownAbs` as it has additional logic for figuring
// it out that we don't want to duplicate here.
KnownBits KnownAbsNoSign = KnownAbs;
KnownBits KnownNegNoSign = KnownNeg;
KnownAbsNoSign.One.clearSignBit();
KnownAbsNoSign.Zero.clearSignBit();
KnownNegNoSign.One.clearSignBit();
KnownNegNoSign.Zero.clearSignBit();

assert(KnownAbsNoSign.One.isSubsetOf(KnownNegNoSign.One));
assert(KnownAbsNoSign.Zero.isSubsetOf(KnownNegNoSign.Zero));

assert(KnownAbs.One.isSubsetOf(KnownNeg.One));
assert(KnownAbs.Zero.isSubsetOf(KnownNeg.Zero));

KnownNeg.One.clearSignBit();
KnownNeg.Zero.clearSignBit();
KnownAbs.One |= KnownNeg.One;
KnownAbs.Zero |= KnownNeg.Zero;
On the exhaustive tests fails:

KnownBits.cpp:440: llvm::KnownBits llvm::KnownBits::abs(bool) const: Assertion `KnownAbs.Zero.isSubsetOf(KnownNeg.Zero)' failed.

nikic added inline comments.May 13 2023, 12:58 PM
llvm/lib/Support/KnownBits.cpp
470

The argument to the NSW flag should be IntMinIsPoison, not KnownAbs.isNegative(). Effectively you're assuming that the flag is always set, so I would expect that to cause exhaustive test failures.

goldstein.w.n added inline comments.May 13 2023, 1:04 PM
llvm/lib/Support/KnownBits.cpp
470

Earlier in the code is:

if (IntMinIsPoison || (!One.isZero() && !One.isMinSignedValue())) {
  KnownAbs.One.clearSignBit();
  KnownAbs.Zero.setSignBit();
}

So if KnownAbs.isNonNegative() is a superset of IntMinPoison.

nikic added inline comments.May 15 2023, 8:17 AM
llvm/lib/Support/KnownBits.cpp
470

Okay, I rewrote our test infrastructure to make optimality failures easier to analyze. For a simple implementation using

if (isNegative())
  return computeForAddSub(/*Add*/ false, /*NSW*/ IntMinIsPoison,
                          KnownBits::makeConstant(APInt(getBitWidth(), 0)),
                          *this);

I get these failures:

/home/npopov/repos/llvm-project/llvm/unittests/Support/KnownBitsTest.cpp:86: Failure
Value of: isOptimal(Exact, Computed, Known)
  Actual: false (Inputs = 1?00, Computed = 0?00, Exact = 0100)
Expected: true
/home/npopov/repos/llvm-project/llvm/unittests/Support/KnownBitsTest.cpp:86: Failure
Value of: isOptimal(Exact, Computed, Known)
  Actual: false (Inputs = 10??, Computed = 0???, Exact = 01??)
Expected: true
/home/npopov/repos/llvm-project/llvm/unittests/Support/KnownBitsTest.cpp:86: Failure
Value of: isOptimal(Exact, Computed, Known)
  Actual: false (Inputs = 10?0, Computed = 0??0, Exact = 0110)
Expected: true
/home/npopov/repos/llvm-project/llvm/unittests/Support/KnownBitsTest.cpp:86: Failure
Value of: isOptimal(Exact, Computed, Known)
  Actual: false (Inputs = 100?, Computed = 0???, Exact = 0111)
Expected: true

Basically, the cases where it is non-optimal are cases where we don't make use of the fact that at least one of the unknown bits must be non-zero, otherwise the input would be IntMin. Those are differences in the non-sign bits though. And the optimality failures (for known negative numbers) that I get for your implementation with the KnownAbs fallback are exactly the same.

goldstein.w.n added inline comments.May 15 2023, 2:12 PM
llvm/lib/Support/KnownBits.cpp
470

Okay, I rewrote our test infrastructure to make optimality failures easier to analyze. For a simple implementation using

if (isNegative())
  return computeForAddSub(/*Add*/ false, /*NSW*/ IntMinIsPoison,
                          KnownBits::makeConstant(APInt(getBitWidth(), 0)),
                          *this);

I get these failures:

/home/npopov/repos/llvm-project/llvm/unittests/Support/KnownBitsTest.cpp:86: Failure
Value of: isOptimal(Exact, Computed, Known)
  Actual: false (Inputs = 1?00, Computed = 0?00, Exact = 0100)
Expected: true
/home/npopov/repos/llvm-project/llvm/unittests/Support/KnownBitsTest.cpp:86: Failure
Value of: isOptimal(Exact, Computed, Known)
  Actual: false (Inputs = 10??, Computed = 0???, Exact = 01??)
Expected: true
/home/npopov/repos/llvm-project/llvm/unittests/Support/KnownBitsTest.cpp:86: Failure
Value of: isOptimal(Exact, Computed, Known)
  Actual: false (Inputs = 10?0, Computed = 0??0, Exact = 0110)
Expected: true
/home/npopov/repos/llvm-project/llvm/unittests/Support/KnownBitsTest.cpp:86: Failure
Value of: isOptimal(Exact, Computed, Known)
  Actual: false (Inputs = 100?, Computed = 0???, Exact = 0111)
Expected: true

Basically, the cases where it is non-optimal are cases where we don't make use of the fact that at least one of the unknown bits must be non-zero, otherwise the input would be IntMin. Those are differences in the non-sign bits though. And the optimality failures (for known negative numbers) that I get for your implementation with the KnownAbs fallback are exactly the same.

You're right. Its only the poison case. Updating. Also think I can cover all these missing cases so will change to full optimal tests.

Fill in the rest of the special cases + only use neg

foad accepted this revision.May 23 2023, 3:24 AM

LGTM. I hope that one day we can remove this logic in abs() when computeForAddSub() gets a precise nsw implementation.

This revision is now accepted and ready to land.May 23 2023, 3:24 AM
RKSimon accepted this revision.May 23 2023, 9:01 AM

LGTM

This revision was landed with ongoing or failed builds.May 23 2023, 11:56 AM
This revision was automatically updated to reflect the committed changes.