https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128513

            Bug ID: 128513
           Summary: wrong code at -O2: combine folds a 65-bit signed
                    __int128 bit-field AND to 0, and loses the sign
                    extension for widths 70..99 since r13-1379
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Keywords: wrong-code
          Severity: normal
          Priority: P3
         Component: rtl-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: jeffchen006 at gmail dot com
  Target Milestone: ---
            Target: x86_64-pc-linux-gnu

Test 1 (65-bit field):

int printf(const char *, ...);

struct S { __int128 x : 65; };
int g = 6;
double d;

int main(void)
{
  struct S t;
  t.x = g;
  d = t.x & -0x100000006LL;
  printf("%g\n", d);
  return 0;
}

$ gcc -O0 t.c && ./a.out
2
$ gcc -O2 t.c && ./a.out
0

Expected 2, because 6 & -0x100000006 = 2.

Test 2 (99-bit field, the sign extension of the high half is lost):

int printf(const char *, ...);

struct S { __int128 x : 99; } s;
__int128 k;

int main(void)
{
  s.x = (__int128)0x400000001 << (64 - k) & ~1;
  printf("%llx\n", (long long)(s.x >> 64));
  return 0;
}

$ gcc -O0 t2.c && ./a.out
fffffffc00000001
$ gcc -O1 t2.c && ./a.out
400000001

(Storing an out-of-range value in a bit-field is implementation-defined. GCC
defines it as modulo 2^N.)

Cause: two RTL simplifications assume that the mode fits in 64 bits.
- simplify_truncation (simplify-rtx.cc) checks (truncate:DI (and:TI
(ashiftrt:TI X 63) C)) with GET_MODE_MASK (TImode). That mask is cut to 64
bits, so the check always passes (test 1).
- simplify_shift_const_1 (combine.cc) calls simplify_and_const_int for a TImode
AND with a negative constant. That drops bits 64..127 (test 2).

First bad commit: r13-1379-ge8a46e5cdab. It changes only i386.md (and:TI now
stays in TImode until after reload), so it exposes these two bugs.
Correct on 12.5. Wrong on 13.4, 14.3, 15.2, 16.2 and trunk.
Avoided by: -fdisable-rtl-combine.

Suggested fix (code part only; the full patch, with a test case and ChangeLog,
is in the attachment):

diff --git a/gcc/combine.cc b/gcc/combine.cc
--- a/gcc/combine.cc
+++ b/gcc/combine.cc
@@ -11385,7 +11385,11 @@ simplify_shift_const_1 (enum rtx_code code,
machine_mode result_mode,
          && GET_MODE_PRECISION (int_result_mode) < HOST_BITS_PER_WIDE_INT)
        outer_const = trunc_int_for_mode (outer_const, int_result_mode);

-      if (outer_op == AND)
+      /* We can call simplify_and_const_int only if we don't lose
+        any (sign) bits when converting OUTER_CONST to
+        "unsigned HOST_WIDE_INT".  */
+      if (outer_op == AND
+         && (HWI_COMPUTABLE_MODE_P (int_result_mode) || outer_const > 0))
        x = simplify_and_const_int (NULL_RTX, int_result_mode, x, outer_const);
       else if (outer_op == SET)
        {
diff --git a/gcc/simplify-rtx.cc b/gcc/simplify-rtx.cc
--- a/gcc/simplify-rtx.cc
+++ b/gcc/simplify-rtx.cc
@@ -715,10 +715,13 @@ simplify_context::simplify_truncation (machine_mode mode,
rtx op,
       unsigned HOST_WIDE_INT mask = UINTVAL (mask_op);

       if (shift < precision
-         /* If doing this transform works for an X with all bits set,
-            it works for any X.  */
-         && ((GET_MODE_MASK (mode) >> shift) & mask)
-            == ((GET_MODE_MASK (op_mode) >> shift) & mask)
+         /* The narrower shift fills the top SHIFT bits of MODE with zeros
+            rather than with bits of X (or copies of its sign bit), so MASK
+            must not select any of those bits.  Use wide_int, because
+            GET_MODE_MASK of OP_MODE is not exact if OP_MODE is wider than
+            a HOST_WIDE_INT.  */
+         && wi::lrshift (wi::shwi (INTVAL (mask_op), precision),
+                         precision - shift) == 0
          && (op0 = simplify_gen_unary (TRUNCATE, mode, op0, op_mode))
          && (op0 = simplify_gen_binary (LSHIFTRT, mode, op0, shift_op)))
        {

Tested: both tests above and all variants; the asm of about 4480 testsuite
files does not change, except the new tests. No bootstrap or make check yet.

UBSan and ASan are clean at -O0.
gcc version 17.0.0 20261029 (experimental), r17-5747-g85867b0c935,
x86_64-pc-linux-gnu

Reply via email to