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

[APInt] Add default-disabled assertion to APInt constructor #106524

Merged
merged 1 commit into from
Sep 2, 2024

Conversation

nikic
Copy link
Contributor

@nikic nikic commented Aug 29, 2024

If the uint64_t constructor is used, assert that the value is actually a signed or unsigned N-bit integer depending on whether the isSigned flag is set. Provide an implicitTrunc flag to restore the previous behavior, where the argument is silently truncated instead.

In this commit, implicitTrunc is enabled by default, which means that the new assertions are disabled and no actual change in behavior occurs. The plan is to flip the default once all places violating the assertion have been fixed. See #80309 for the scope of the necessary changes.

The primary motivation for this change is to avoid incorrectly specified isSigned flags. A recurring problem we have is that people write something like APInt(BW, -1) and this works perfectly fine -- until the code path is hit with BW > 64. Most of our i128 specific miscompilations are caused by variants of this issue.

The cost of the change is that we have to specify the correct isSigned flag (and make sure there are no excess bits) for uses where BW is always <= 64 as well.

If the uint64_t constructor is used, assert that the value is
actually a signed or unsigned N-bit integer depending on whether
the isSigned flag is set. Provide an implicitTrunc flag to
restore the previous behavior, where the argument is silently
truncated instead.

In this commit, implicitTrunc is enabled by default, which means
that the new assertions are disabled and no actual change in
behavior occurs. The plan is to flip the default once all places
violating the assertion have been fixed. See llvm#80309 for the scope
of the necessary changes.

The primary motivation for this change is to avoid incorrectly
specified isSigned flags. A recurring problem we have is that
people write something like `APInt(BW, -1)` and this works
perfectly fine -- until the code path is hit with `BW > 64`.
Most of our i128 specific miscompilations are caused by variants
of this issue.

The cost of the change is that we have to specify the correct
isSigned flag (and make sure there are no excess bits) for uses
where BW is always <= 64 as well.
@llvmbot
Copy link

llvmbot commented Aug 29, 2024

@llvm/pr-subscribers-llvm-adt

@llvm/pr-subscribers-llvm-support

Author: Nikita Popov (nikic)

Changes

If the uint64_t constructor is used, assert that the value is actually a signed or unsigned N-bit integer depending on whether the isSigned flag is set. Provide an implicitTrunc flag to restore the previous behavior, where the argument is silently truncated instead.

In this commit, implicitTrunc is enabled by default, which means that the new assertions are disabled and no actual change in behavior occurs. The plan is to flip the default once all places violating the assertion have been fixed. See #80309 for the scope of the necessary changes.

The primary motivation for this change is to avoid incorrectly specified isSigned flags. A recurring problem we have is that people write something like APInt(BW, -1) and this works perfectly fine -- until the code path is hit with BW &gt; 64. Most of our i128 specific miscompilations are caused by variants of this issue.

The cost of the change is that we have to specify the correct isSigned flag (and make sure there are no excess bits) for uses where BW is always <= 64 as well.


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

3 Files Affected:

  • (modified) llvm/include/llvm/ADT/APInt.h (+17-2)
  • (modified) llvm/lib/Support/APInt.cpp (+9-5)
  • (modified) llvm/unittests/ADT/APIntTest.cpp (+3-2)
diff --git a/llvm/include/llvm/ADT/APInt.h b/llvm/include/llvm/ADT/APInt.h
index 65ba3f15305c78..a42dae8887392d 100644
--- a/llvm/include/llvm/ADT/APInt.h
+++ b/llvm/include/llvm/ADT/APInt.h
@@ -106,11 +106,26 @@ class [[nodiscard]] APInt {
   /// \param numBits the bit width of the constructed APInt
   /// \param val the initial value of the APInt
   /// \param isSigned how to treat signedness of val
-  APInt(unsigned numBits, uint64_t val, bool isSigned = false)
+  /// \param implicitTrunc allow implicit truncation of non-zero/sign bits of
+  ///                      val beyond the range of numBits
+  APInt(unsigned numBits, uint64_t val, bool isSigned = false,
+        bool implicitTrunc = true)
       : BitWidth(numBits) {
+    if (!implicitTrunc) {
+      if (BitWidth == 0) {
+        assert(val == 0 && "Value must be zero for 0-bit APInt");
+      } else if (isSigned) {
+        assert(llvm::isIntN(BitWidth, val) &&
+               "Value is not an N-bit signed value");
+      } else {
+        assert(llvm::isUIntN(BitWidth, val) &&
+               "Value is not an N-bit unsigned value");
+      }
+    }
     if (isSingleWord()) {
       U.VAL = val;
-      clearUnusedBits();
+      if (implicitTrunc || isSigned)
+        clearUnusedBits();
     } else {
       initSlowCase(val, isSigned);
     }
diff --git a/llvm/lib/Support/APInt.cpp b/llvm/lib/Support/APInt.cpp
index fe22e9ba04b6f5..78d573966c6c99 100644
--- a/llvm/lib/Support/APInt.cpp
+++ b/llvm/lib/Support/APInt.cpp
@@ -234,7 +234,8 @@ APInt& APInt::operator-=(uint64_t RHS) {
 APInt APInt::operator*(const APInt& RHS) const {
   assert(BitWidth == RHS.BitWidth && "Bit widths must be the same");
   if (isSingleWord())
-    return APInt(BitWidth, U.VAL * RHS.U.VAL);
+    return APInt(BitWidth, U.VAL * RHS.U.VAL, /*isSigned=*/false,
+                 /*implicitTrunc=*/true);
 
   APInt Result(getMemory(getNumWords()), getBitWidth());
   tcMultiply(Result.U.pVal, U.pVal, RHS.U.pVal, getNumWords());
@@ -455,7 +456,8 @@ APInt APInt::extractBits(unsigned numBits, unsigned bitPosition) const {
          "Illegal bit extraction");
 
   if (isSingleWord())
-    return APInt(numBits, U.VAL >> bitPosition);
+    return APInt(numBits, U.VAL >> bitPosition, /*isSigned=*/false,
+                 /*implicitTrunc=*/true);
 
   unsigned loBit = whichBit(bitPosition);
   unsigned loWord = whichWord(bitPosition);
@@ -463,7 +465,8 @@ APInt APInt::extractBits(unsigned numBits, unsigned bitPosition) const {
 
   // Single word result extracting bits from a single word source.
   if (loWord == hiWord)
-    return APInt(numBits, U.pVal[loWord] >> loBit);
+    return APInt(numBits, U.pVal[loWord] >> loBit, /*isSigned=*/false,
+                 /*implicitTrunc=*/true);
 
   // Extracting bits that start on a source word boundary can be done
   // as a fast memory copy.
@@ -907,7 +910,8 @@ APInt APInt::trunc(unsigned width) const {
   assert(width <= BitWidth && "Invalid APInt Truncate request");
 
   if (width <= APINT_BITS_PER_WORD)
-    return APInt(width, getRawData()[0]);
+    return APInt(width, getRawData()[0], /*isSigned=*/false,
+                 /*implicitTrunc=*/true);
 
   if (width == BitWidth)
     return *this;
@@ -955,7 +959,7 @@ APInt APInt::sext(unsigned Width) const {
   assert(Width >= BitWidth && "Invalid APInt SignExtend request");
 
   if (Width <= APINT_BITS_PER_WORD)
-    return APInt(Width, SignExtend64(U.VAL, BitWidth));
+    return APInt(Width, SignExtend64(U.VAL, BitWidth), /*isSigned=*/true);
 
   if (Width == BitWidth)
     return *this;
diff --git a/llvm/unittests/ADT/APIntTest.cpp b/llvm/unittests/ADT/APIntTest.cpp
index eb4b847185f53b..fff29d24a05299 100644
--- a/llvm/unittests/ADT/APIntTest.cpp
+++ b/llvm/unittests/ADT/APIntTest.cpp
@@ -220,11 +220,12 @@ TEST(APIntTest, i256) {
 }
 
 TEST(APIntTest, i1) {
-  const APInt neg_two(1, static_cast<uint64_t>(-2), true);
+  const APInt neg_two(1, static_cast<uint64_t>(-2), true,
+                      /*implicitTrunc=*/true);
   const APInt neg_one(1, static_cast<uint64_t>(-1), true);
   const APInt zero(1, 0);
   const APInt one(1, 1);
-  const APInt two(1, 2);
+  const APInt two(1, 2, false, /*implicitTrunc=*/true);
 
   EXPECT_EQ(0, neg_two.getSExtValue());
   EXPECT_EQ(-1, neg_one.getSExtValue());

Copy link
Collaborator

@RKSimon RKSimon left a comment

Choose a reason for hiding this comment

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

LGTM

@nikic nikic merged commit 30cc198 into llvm:main Sep 2, 2024
11 checks passed
@nikic nikic deleted the apint-implicit-trunc branch September 2, 2024 07:48
nikic added a commit that referenced this pull request Sep 20, 2024
This makes unit tests compatible with the assertion added in
#106524, by setting the
isSigned flag to the correct value or changing how the value is
constructed.
nikic added a commit to nikic/llvm-project that referenced this pull request Sep 30, 2024
This fixes all the places in MLIR that hit the new assertion added
in llvm#106524, in preparation for enabling it by default. That is,
cases where the value passed to the APInt constructor is not an N-bit
signed/unsigned integer, where N is the bit width and signedness is
determined by the isSigned flag.

The fixes either set the correct value for isSigned, or set the
implicitTrunc flag to retain the old behavior. I've left TODOs
for the latter case in some places, where I think that it may be
worthwhile to stop doing implicit truncation in the future.

Note that the assertion is currently still disabled by default, so
this patch is mostly NFC.
xgupta pushed a commit to xgupta/llvm-project that referenced this pull request Oct 4, 2024
This makes unit tests compatible with the assertion added in
llvm#106524, by setting the
isSigned flag to the correct value or changing how the value is
constructed.
nikic added a commit that referenced this pull request Oct 14, 2024
…unc (#110466)

This fixes all the places in MLIR that hit the new assertion added in
#106524, in preparation for enabling it by default. That is, cases where
the value passed to the APInt constructor is not an N-bit
signed/unsigned integer, where N is the bit width and signedness is
determined by the isSigned flag.

The fixes either set the correct value for isSigned, or set the
implicitTrunc flag to retain the old behavior. I've left TODOs for the
latter case in some places, where I think that it may be worthwhile to
stop doing implicit truncation in the future.

Note that the assertion is currently still disabled by default, so this
patch is mostly NFC.

This is just the MLIR changes split off from
#80309.
DanielCChen pushed a commit to DanielCChen/llvm-project that referenced this pull request Oct 16, 2024
…unc (llvm#110466)

This fixes all the places in MLIR that hit the new assertion added in
llvm#106524, in preparation for enabling it by default. That is, cases where
the value passed to the APInt constructor is not an N-bit
signed/unsigned integer, where N is the bit width and signedness is
determined by the isSigned flag.

The fixes either set the correct value for isSigned, or set the
implicitTrunc flag to retain the old behavior. I've left TODOs for the
latter case in some places, where I think that it may be worthwhile to
stop doing implicit truncation in the future.

Note that the assertion is currently still disabled by default, so this
patch is mostly NFC.

This is just the MLIR changes split off from
llvm#80309.
nikic added a commit that referenced this pull request Oct 17, 2024
…CI) (#80309)

This fixes all the places that hit the new assertion added in
#106524 in tests. That is,
cases where the value passed to the APInt constructor is not an N-bit
signed/unsigned integer, where N is the bit width and signedness is
determined by the isSigned flag.

The fixes either set the correct value for isSigned, set the
implicitTrunc flag, or perform more calculations inside APInt.

Note that the assertion is currently still disabled by default, so this
patch is mostly NFC.
nikic added a commit to nikic/llvm-project that referenced this pull request Oct 17, 2024
This enables the assertion introduced in
llvm#106524, which checks
that the value passed to the constructor is indeed a valid
N-bit signed or unsigned integer.

Places that previously violated the assertion were updated in
advance, e.g. in llvm#80309.

It is possible to opt-out of the check and restore the previous
behavior by setting implicitTrunc=true.
bricknerb pushed a commit to bricknerb/llvm-project that referenced this pull request Oct 17, 2024
…unc (llvm#110466)

This fixes all the places in MLIR that hit the new assertion added in
llvm#106524, in preparation for enabling it by default. That is, cases where
the value passed to the APInt constructor is not an N-bit
signed/unsigned integer, where N is the bit width and signedness is
determined by the isSigned flag.

The fixes either set the correct value for isSigned, or set the
implicitTrunc flag to retain the old behavior. I've left TODOs for the
latter case in some places, where I think that it may be worthwhile to
stop doing implicit truncation in the future.

Note that the assertion is currently still disabled by default, so this
patch is mostly NFC.

This is just the MLIR changes split off from
llvm#80309.
bricknerb pushed a commit to bricknerb/llvm-project that referenced this pull request Oct 17, 2024
…CI) (llvm#80309)

This fixes all the places that hit the new assertion added in
llvm#106524 in tests. That is,
cases where the value passed to the APInt constructor is not an N-bit
signed/unsigned integer, where N is the bit width and signedness is
determined by the isSigned flag.

The fixes either set the correct value for isSigned, set the
implicitTrunc flag, or perform more calculations inside APInt.

Note that the assertion is currently still disabled by default, so this
patch is mostly NFC.
nikic added a commit that referenced this pull request Oct 18, 2024
This enables the assertion introduced in
#106524, which checks that the
value passed to the APInt constructor is indeed a valid N-bit signed or
unsigned integer.

Places that previously violated the assertion were updated in advance,
e.g. in #80309.

It is possible to opt-out of the check and restore the previous behavior
by setting implicitTrunc=true.
bricknerb pushed a commit to bricknerb/llvm-project that referenced this pull request Oct 21, 2024
This enables the assertion introduced in
llvm#106524, which checks that the
value passed to the APInt constructor is indeed a valid N-bit signed or
unsigned integer.

Places that previously violated the assertion were updated in advance,
e.g. in llvm#80309.

It is possible to opt-out of the check and restore the previous behavior
by setting implicitTrunc=true.
EricWF pushed a commit to efcs/llvm-project that referenced this pull request Oct 22, 2024
…unc (llvm#110466)

This fixes all the places in MLIR that hit the new assertion added in
llvm#106524, in preparation for enabling it by default. That is, cases where
the value passed to the APInt constructor is not an N-bit
signed/unsigned integer, where N is the bit width and signedness is
determined by the isSigned flag.

The fixes either set the correct value for isSigned, or set the
implicitTrunc flag to retain the old behavior. I've left TODOs for the
latter case in some places, where I think that it may be worthwhile to
stop doing implicit truncation in the future.

Note that the assertion is currently still disabled by default, so this
patch is mostly NFC.

This is just the MLIR changes split off from
llvm#80309.
EricWF pushed a commit to efcs/llvm-project that referenced this pull request Oct 22, 2024
…CI) (llvm#80309)

This fixes all the places that hit the new assertion added in
llvm#106524 in tests. That is,
cases where the value passed to the APInt constructor is not an N-bit
signed/unsigned integer, where N is the bit width and signedness is
determined by the isSigned flag.

The fixes either set the correct value for isSigned, set the
implicitTrunc flag, or perform more calculations inside APInt.

Note that the assertion is currently still disabled by default, so this
patch is mostly NFC.
EricWF pushed a commit to efcs/llvm-project that referenced this pull request Oct 22, 2024
This enables the assertion introduced in
llvm#106524, which checks that the
value passed to the APInt constructor is indeed a valid N-bit signed or
unsigned integer.

Places that previously violated the assertion were updated in advance,
e.g. in llvm#80309.

It is possible to opt-out of the check and restore the previous behavior
by setting implicitTrunc=true.
topperc added a commit to topperc/llvm-project that referenced this pull request Oct 31, 2024
…stant. NFC

This assert is also present inside the APInt constructor after llvm#106524,
but its not yet enabled by default. So we also need to pass false
to the implicitTrunc flag.
nikic added a commit to nikic/llvm-project that referenced this pull request Nov 1, 2024
This enables the assertion introduced in
llvm#106524, which checks
that the value passed to the constructor is indeed a valid
N-bit signed or unsigned integer.

Places that previously violated the assertion were updated in
advance, e.g. in llvm#80309.

It is possible to opt-out of the check and restore the previous
behavior by setting implicitTrunc=true.

-----

The buildbot failures from the previous attempt should be fixed
by a18dd29 and
e2074c6.
nikic added a commit that referenced this pull request Nov 1, 2024
This enables the assertion introduced in
#106524, which checks that the
value passed to the constructor is indeed a valid N-bit signed or
unsigned integer.

Places that previously violated the assertion were updated in advance,
e.g. in #80309.

It is possible to opt-out of the check and restore the previous behavior
by setting implicitTrunc=true.

-----

The buildbot failures from the previous attempt should be fixed by
a18dd29 and
e2074c6.
smallp-o-p pushed a commit to smallp-o-p/llvm-project that referenced this pull request Nov 3, 2024
This enables the assertion introduced in
llvm#106524, which checks that the
value passed to the constructor is indeed a valid N-bit signed or
unsigned integer.

Places that previously violated the assertion were updated in advance,
e.g. in llvm#80309.

It is possible to opt-out of the check and restore the previous behavior
by setting implicitTrunc=true.

-----

The buildbot failures from the previous attempt should be fixed by
a18dd29 and
e2074c6.
smallp-o-p pushed a commit to smallp-o-p/llvm-project that referenced this pull request Nov 3, 2024
This enables the assertion introduced in
llvm#106524, which checks that the
value passed to the constructor is indeed a valid N-bit signed or
unsigned integer.

Places that previously violated the assertion were updated in advance,
e.g. in llvm#80309.

It is possible to opt-out of the check and restore the previous behavior
by setting implicitTrunc=true.

-----

The buildbot failures from the previous attempt should be fixed by
a18dd29 and
e2074c6.
NoumanAmir657 pushed a commit to NoumanAmir657/llvm-project that referenced this pull request Nov 4, 2024
This enables the assertion introduced in
llvm#106524, which checks that the
value passed to the constructor is indeed a valid N-bit signed or
unsigned integer.

Places that previously violated the assertion were updated in advance,
e.g. in llvm#80309.

It is possible to opt-out of the check and restore the previous behavior
by setting implicitTrunc=true.

-----

The buildbot failures from the previous attempt should be fixed by
a18dd29 and
e2074c6.
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.

3 participants