https://gcc.gnu.org/g:6c64cdc8a3ff6508f32c785eb070f08c596dc003
commit 6c64cdc8a3ff6508f32c785eb070f08c596dc003 Author: Alexandre Oliva <ol...@gnu.org> Date: Mon Jan 13 14:53:25 2025 -0300 [ifcombine] set mask to clip sign-extended constant Diff: --- gcc/gimple-fold.cc | 152 +++++++++++++++++++++------------- gcc/testsuite/gcc.dg/field-merge-21.c | 52 ++++++++++++ gcc/testsuite/gcc.dg/field-merge-22.c | 30 +++++++ 3 files changed, 178 insertions(+), 56 deletions(-) diff --git a/gcc/gimple-fold.cc b/gcc/gimple-fold.cc index 93ed8b3abb05..f79faf21a5f7 100644 --- a/gcc/gimple-fold.cc +++ b/gcc/gimple-fold.cc @@ -7510,8 +7510,7 @@ gimple_binop_def_p (enum tree_code code, tree t, tree op[2]) *PREVERSEP is set to the storage order of the field. *PAND_MASK is set to the mask found in a BIT_AND_EXPR, if any. If - PAND_MASK *is NULL, BIT_AND_EXPR is not recognized. If *PAND_MASK - is initially set to a mask with nonzero precision, that mask is + *PAND_MASK is initially set to a mask with nonzero precision, that mask is combined with the found mask, or adjusted in precision to match. *PSIGNBIT is set to TRUE if, before clipping to *PBITSIZE, the mask @@ -7539,7 +7538,7 @@ decode_field_reference (tree *pexp, HOST_WIDE_INT *pbitsize, bool *punsignedp, bool *preversep, bool *pvolatilep, wide_int *pand_mask, bool *psignbit, bool *xor_p, tree *xor_cmp_op, wide_int *xor_pand_mask, - gimple **load, location_t loc[4]) + gimple **loadp, location_t loc[4]) { tree exp = *pexp; tree outer_type = 0; @@ -7549,9 +7548,11 @@ decode_field_reference (tree *pexp, HOST_WIDE_INT *pbitsize, tree res_ops[2]; machine_mode mode; bool convert_before_shift = false; - - *load = NULL; - *psignbit = false; + bool signbit = false; + bool xorp = false; + tree xor_op; + wide_int xor_mask; + gimple *load = NULL; /* All the optimizations using this function assume integer fields. There are problems with FP fields since the type_for_size call @@ -7576,7 +7577,7 @@ decode_field_reference (tree *pexp, HOST_WIDE_INT *pbitsize, /* Recognize and save a masking operation. Combine it with an incoming mask. */ - if (pand_mask && gimple_binop_def_p (BIT_AND_EXPR, exp, res_ops) + if (gimple_binop_def_p (BIT_AND_EXPR, exp, res_ops) && TREE_CODE (res_ops[1]) == INTEGER_CST) { loc[1] = gimple_location (SSA_NAME_DEF_STMT (exp)); @@ -7596,7 +7597,7 @@ decode_field_reference (tree *pexp, HOST_WIDE_INT *pbitsize, and_mask &= wide_int::from (*pand_mask, prec_op, UNSIGNED); } } - else if (pand_mask) + else and_mask = *pand_mask; /* Turn (a ^ b) [!]= 0 into a [!]= b. */ @@ -7615,10 +7616,10 @@ decode_field_reference (tree *pexp, HOST_WIDE_INT *pbitsize, return NULL_TREE; else if (integer_zerop (*xor_cmp_op)) { - *xor_p = true; + xorp = true; exp = res_ops[0]; - *xor_cmp_op = *pexp; - *xor_pand_mask = *pand_mask; + xor_op = *pexp; + xor_mask = *pand_mask; } } @@ -7646,12 +7647,12 @@ decode_field_reference (tree *pexp, HOST_WIDE_INT *pbitsize, /* Yet another chance to drop conversions. This one is allowed to match a converting load, subsuming the load identification block below. */ - if (!outer_type && gimple_convert_def_p (exp, res_ops, load)) + if (!outer_type && gimple_convert_def_p (exp, res_ops, &load)) { outer_type = TREE_TYPE (exp); loc[0] = gimple_location (SSA_NAME_DEF_STMT (exp)); - if (*load) - loc[3] = gimple_location (*load); + if (load) + loc[3] = gimple_location (load); exp = res_ops[0]; /* This looks backwards, but we're going back the def chain, so if we find the conversion here, after finding a shift, that's because the @@ -7662,14 +7663,13 @@ decode_field_reference (tree *pexp, HOST_WIDE_INT *pbitsize, } /* Identify the load, if there is one. */ - if (!(*load) && TREE_CODE (exp) == SSA_NAME - && !SSA_NAME_IS_DEFAULT_DEF (exp)) + if (!load && TREE_CODE (exp) == SSA_NAME && !SSA_NAME_IS_DEFAULT_DEF (exp)) { gimple *def = SSA_NAME_DEF_STMT (exp); if (gimple_assign_load_p (def)) { loc[3] = gimple_location (def); - *load = def; + load = def; exp = gimple_assign_rhs1 (def); } } @@ -7694,63 +7694,75 @@ decode_field_reference (tree *pexp, HOST_WIDE_INT *pbitsize, && !type_has_mode_precision_p (TREE_TYPE (inner)))) return NULL_TREE; - *pbitsize = bs; - *pbitpos = bp; - *punsignedp = unsignedp; - *preversep = reversep; - *pvolatilep = volatilep; - /* Adjust shifts... */ if (convert_before_shift - && outer_type && *pbitsize > TYPE_PRECISION (outer_type)) + && outer_type && bs > TYPE_PRECISION (outer_type)) { - HOST_WIDE_INT excess = *pbitsize - TYPE_PRECISION (outer_type); - if (*preversep ? !BYTES_BIG_ENDIAN : BYTES_BIG_ENDIAN) - *pbitpos += excess; - *pbitsize -= excess; + HOST_WIDE_INT excess = bs - TYPE_PRECISION (outer_type); + if (reversep ? !BYTES_BIG_ENDIAN : BYTES_BIG_ENDIAN) + bp += excess; + bs -= excess; } if (shiftrt) { - if (!*preversep ? !BYTES_BIG_ENDIAN : BYTES_BIG_ENDIAN) - *pbitpos += shiftrt; - *pbitsize -= shiftrt; + /* Punt if we're shifting by more than the loaded bitfield (after + adjustment), or if there's a shift after a change of signedness, punt. + When comparing this field with a constant, we'll check that the + constant is a proper sign- or zero-extension (depending on signedness) + of a value that would fit in the selected portion of the bitfield. A + shift after a change of signedness would make the extension + non-uniform, and we can't deal with that (yet ???). See + gcc.dg/field-merge-22.c for a test that would go wrong. */ + if (bs <= shiftrt || unsignedp != TYPE_UNSIGNED (outer_type)) + return NULL_TREE; + if (!reversep ? !BYTES_BIG_ENDIAN : BYTES_BIG_ENDIAN) + bp += shiftrt; + bs -= shiftrt; } /* ... and bit position. */ if (!convert_before_shift - && outer_type && *pbitsize > TYPE_PRECISION (outer_type)) + && outer_type && bs > TYPE_PRECISION (outer_type)) { - HOST_WIDE_INT excess = *pbitsize - TYPE_PRECISION (outer_type); - if (*preversep ? !BYTES_BIG_ENDIAN : BYTES_BIG_ENDIAN) - *pbitpos += excess; - *pbitsize -= excess; + HOST_WIDE_INT excess = bs - TYPE_PRECISION (outer_type); + if (reversep ? !BYTES_BIG_ENDIAN : BYTES_BIG_ENDIAN) + bp += excess; + bs -= excess; } - *pexp = exp; - /* If the number of bits in the reference is the same as the bitsize of the outer type, then the outer type gives the signedness. Otherwise (in case of a small bitfield) the signedness is unchanged. */ - if (outer_type && *pbitsize == TYPE_PRECISION (outer_type)) - *punsignedp = TYPE_UNSIGNED (outer_type); + if (outer_type && bs == TYPE_PRECISION (outer_type)) + unsignedp = TYPE_UNSIGNED (outer_type); - if (pand_mask) + /* Make the mask the expected width. */ + if (and_mask.get_precision () != 0) { - /* Make the mask the expected width. */ - if (and_mask.get_precision () != 0) - { - /* If the AND_MASK encompasses bits that would be extensions of - the sign bit, set *PSIGNBIT. */ - if (!unsignedp - && and_mask.get_precision () > *pbitsize - && (and_mask - & wi::mask (*pbitsize, true, and_mask.get_precision ())) != 0) - *psignbit = true; - and_mask = wide_int::from (and_mask, *pbitsize, UNSIGNED); - } + /* If the AND_MASK encompasses bits that would be extensions of + the sign bit, set SIGNBIT. */ + if (!unsignedp + && and_mask.get_precision () > bs + && (and_mask & wi::mask (bs, true, and_mask.get_precision ())) != 0) + signbit = true; + and_mask = wide_int::from (and_mask, bs, UNSIGNED); + } - *pand_mask = and_mask; + *pexp = exp; + *loadp = load; + *pbitsize = bs; + *pbitpos = bp; + *punsignedp = unsignedp; + *preversep = reversep; + *pvolatilep = volatilep; + *psignbit = signbit; + *pand_mask = and_mask; + if (xorp) + { + *xor_p = xorp; + *xor_cmp_op = xor_op; + *xor_pand_mask = xor_mask; } return inner; @@ -8512,13 +8524,25 @@ fold_truth_andor_for_ifcombine (enum tree_code code, tree truth_type, and bit position. */ if (l_const.get_precision ()) { + /* Before clipping upper bits of the right-hand operand of the compare, + check that they're sign or zero extensions, depending on how the + left-hand operand would be extended. */ + bool l_non_ext_bits = false; + if (ll_bitsize < lr_bitsize) + { + wide_int zext = wi::zext (l_const, ll_bitsize); + if ((ll_unsignedp ? zext : wi::sext (l_const, ll_bitsize)) == l_const) + l_const = zext; + else + l_non_ext_bits = true; + } /* We're doing bitwise equality tests, so don't bother with sign extensions. */ l_const = wide_int::from (l_const, lnprec, UNSIGNED); if (ll_and_mask.get_precision ()) l_const &= wide_int::from (ll_and_mask, lnprec, UNSIGNED); l_const <<= xll_bitpos; - if ((l_const & ~ll_mask) != 0) + if (l_non_ext_bits || (l_const & ~ll_mask) != 0) { warning_at (lloc, OPT_Wtautological_compare, "comparison is always %d", wanted_code == NE_EXPR); @@ -8530,11 +8554,23 @@ fold_truth_andor_for_ifcombine (enum tree_code code, tree truth_type, again. */ gcc_checking_assert (r_const.get_precision ()); + /* Before clipping upper bits of the right-hand operand of the compare, + check that they're sign or zero extensions, depending on how the + left-hand operand would be extended. */ + bool r_non_ext_bits = false; + if (ll_bitsize < lr_bitsize) + { + wide_int zext = wi::zext (r_const, rl_bitsize); + if ((rl_unsignedp ? zext : wi::sext (r_const, rl_bitsize)) == r_const) + r_const = zext; + else + r_non_ext_bits = true; + } r_const = wide_int::from (r_const, lnprec, UNSIGNED); if (rl_and_mask.get_precision ()) r_const &= wide_int::from (rl_and_mask, lnprec, UNSIGNED); r_const <<= xrl_bitpos; - if ((r_const & ~rl_mask) != 0) + if (r_non_ext_bits || (r_const & ~rl_mask) != 0) { warning_at (rloc, OPT_Wtautological_compare, "comparison is always %d", wanted_code == NE_EXPR); @@ -8586,6 +8622,10 @@ fold_truth_andor_for_ifcombine (enum tree_code code, tree truth_type, storage order mismatch occurs between the left and right sides. */ else { + /* When we set l_const, we also set r_const, so we need not test it + again. */ + gcc_checking_assert (!r_const.get_precision ()); + if (ll_bitsize != lr_bitsize || rl_bitsize != rr_bitsize || ll_unsignedp != lr_unsignedp || rl_unsignedp != rr_unsignedp || ll_reversep != lr_reversep diff --git a/gcc/testsuite/gcc.dg/field-merge-21.c b/gcc/testsuite/gcc.dg/field-merge-21.c new file mode 100644 index 000000000000..9e380c68c063 --- /dev/null +++ b/gcc/testsuite/gcc.dg/field-merge-21.c @@ -0,0 +1,52 @@ +/* { dg-do run } */ +/* { dg-options "-O2" } */ + +/* PR tree-optimization/118456 */ +/* Check that shifted fields compared with a constants compare correctly even + if the constant contains sign-extension bits not present in the bit + range. */ + +struct S { unsigned long long o; unsigned short a, b; } s; + +__attribute__((noipa)) int +foo (void) +{ + return ((unsigned char) s.a) >> 3 == 17 && ((signed char) s.b) >> 2 == -27; +} + +__attribute__((noipa)) int +bar (void) +{ + return ((unsigned char) s.a) >> 3 == 17 && ((signed char) s.b) >> 2 == -91; +} + +__attribute__((noipa)) int +bars (void) +{ + return ((unsigned char) s.a) >> 3 == 17 && ((signed char) s.b) >> 2 == 37; +} + +__attribute__((noipa)) int +baz (void) +{ + return ((unsigned char) s.a) >> 3 == 49 && ((signed char) s.b) >> 2 == -27; +} + +__attribute__((noipa)) int +bazs (void) +{ + return ((unsigned char) s.a) >> 3 == (unsigned char) -15 && ((signed char) s.b) >> 2 == -27; +} + +int +main () +{ + s.a = 17 << 3; + s.b = -27u << 2; + if (foo () != 1 + || bar () != 0 + || bars () != 0 + || baz () != 0 + || bazs () != 0) + __builtin_abort (); +} diff --git a/gcc/testsuite/gcc.dg/field-merge-22.c b/gcc/testsuite/gcc.dg/field-merge-22.c new file mode 100644 index 000000000000..82eb8447616f --- /dev/null +++ b/gcc/testsuite/gcc.dg/field-merge-22.c @@ -0,0 +1,30 @@ +/* { dg-do run } */ +/* { dg-options "-O2" } */ + +/* PR tree-optimization/118456 */ +/* Check that compares with constants take into account sign/zero extension of + both the bitfield and of the shifting type. */ + +#define shift (__CHAR_BIT__ - 4) + +struct S { + signed char a : shift + 2; + signed char b : shift + 2; + short ignore[0]; +} s; + +__attribute__((noipa)) int +foo (void) +{ + return ((unsigned char) s.a) >> shift == 15 + && ((unsigned char) s.b) >> shift == 0; +} + +int +main () +{ + s.a = -1; + s.b = 1; + if (foo () != 1) + __builtin_abort (); +}