On Tue, Aug 25, 2026 at 6:20 AM Jian Li <[email protected]> wrote:
>
> This patch allows the operator_plus::op1_op2_relation_effect to record
> the known least significant bit (which is zero) for both full and partial
> equivalences of PLUS_EXPR operands.
>
> The normal update_bitmask hook, which computes a bitmask from operand ranges,
> previously replaced the refined bitmask from the relation effect if run
> afterward,
> losing the known-zero bit. This patch adjusts the order to run normal bitmask
> propagation first and apply the relation effect last, ensuring the constraint
> from the relation intersects with the complete result.
>
> Existing range-op tests have been expanded to include selftests for VREL_EQ
> and partial equivalences. Additionally, an EVRP regression test with DOM
> disabled
> and a -ftrapv test (to verify the retention of potentially trapping additions)
> have been added.
>
> Bootstrapped and regression tested on x86_64-pc-linux-gnu.
>
> PR tree-optimization/126877
>
> gcc/ChangeLog:
>
> * range-op-mixed.h (class operator_plus): Expose and override
> op1_op2_relation_effect.
> * range-op.cc (range_operator::fold_range): Apply relation effects
> after normal bitmask propagation.
> (operator_plus::op1_op2_relation_effect): New.
> (range_op_plus_relation_tests): New.
> (range_op_tests): Call it.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.dg/tree-ssa/pr126877.c: New test.
> * gcc.dg/tree-ssa/pr126877-2.c: New test.
>
> Signed-off-by: Jian Li <[email protected]>
> ---
> gcc/range-op-mixed.h | 6 +++
> gcc/range-op.cc | 51 ++++++++++++++++++++--
> gcc/testsuite/gcc.dg/tree-ssa/pr126877-2.c | 15 +++++++
> gcc/testsuite/gcc.dg/tree-ssa/pr126877.c | 18 ++++++++
> 4 files changed, 86 insertions(+), 4 deletions(-)
> create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr126877-2.c
> create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr126877.c
>
> diff --git a/gcc/range-op-mixed.h b/gcc/range-op-mixed.h
> index 0e97fb11688..bb0034ebf19 100644
> --- a/gcc/range-op-mixed.h
> +++ b/gcc/range-op-mixed.h
> @@ -576,6 +576,7 @@ public:
> using range_operator::lhs_op1_relation;
> using range_operator::lhs_op2_relation;
> using range_operator::update_bitmask;
> + using range_operator::op1_op2_relation_effect;
> bool op1_range (irange &r, tree type,
> const irange &lhs, const irange &op2,
> relation_trio) const final override;
> @@ -599,6 +600,11 @@ public:
> void update_bitmask (irange &r, const irange &lh,
> const irange &rh) const final override;
>
> + bool op1_op2_relation_effect (irange &lhs_range, tree type,
> + const irange &op1_range,
> + const irange &op2_range,
> + relation_kind rel) const final override;
> +
> bool overflow_free_p (const irange &lh, const irange &rh,
> relation_trio = TRIO_VARYING) const final override;
> // Check compatibility of all operands.
> diff --git a/gcc/range-op.cc b/gcc/range-op.cc
> index 4199300f4c8..906bad196b1 100644
> --- a/gcc/range-op.cc
> +++ b/gcc/range-op.cc
> @@ -735,8 +735,8 @@ range_operator::fold_range (irange &r, tree type,
> if (r.varying_p ())
> break;
> }
> - op1_op2_relation_effect (r, type, lh, rh, rel);
> update_bitmask (r, lh, rh);
> + op1_op2_relation_effect (r, type, lh, rh, rel);
> return true;
I am kind of cautious of this change. Maybe we should do the
update_bitmask only inside operator_plus::op1_op2_relation_effect for
the relation_equiv_p case since that is the only case I think needs it
beforehand.
I can think of times where the update_bitmask afterwards would be
needed after op1_op2_relation_effect (since many intersects with
either zero or non-zero or non-negative in another case).
> }
>
> @@ -747,8 +747,8 @@ range_operator::fold_range (irange &r, tree type,
> {
> wi_fold_in_parts (r, type, lh.lower_bound (), lh.upper_bound (),
> rh.lower_bound (), rh.upper_bound ());
> - op1_op2_relation_effect (r, type, lh, rh, rel);
> update_bitmask (r, lh, rh);
> + op1_op2_relation_effect (r, type, lh, rh, rel);
> return true;
> }
>
> @@ -765,13 +765,13 @@ range_operator::fold_range (irange &r, tree type,
> r.union_ (tmp);
> if (r.varying_p ())
> {
> - op1_op2_relation_effect (r, type, lh, rh, rel);
> update_bitmask (r, lh, rh);
> + op1_op2_relation_effect (r, type, lh, rh, rel);
> return true;
> }
> }
> - op1_op2_relation_effect (r, type, lh, rh, rel);
> update_bitmask (r, lh, rh);
> + op1_op2_relation_effect (r, type, lh, rh, rel);
> return true;
> }
>
> @@ -1716,6 +1716,28 @@ operator_plus::update_bitmask (irange &r, const irange
> &lh,
> update_known_bitmask (r, PLUS_EXPR, lh, rh);
> }
>
> +// If both operands are equivalent, their low bit is the same, so the low
> +// bit of their sum is known to be zero.
> +
> +bool
> +operator_plus::op1_op2_relation_effect (irange &lhs_range, tree type,
> + const irange &,
> + const irange &,
> + relation_kind rel) const
> +{
> + if (!relation_equiv_p (rel)
> + || TYPE_OVERFLOW_TRAPS (type))
> + return false;
> +
> + unsigned prec = TYPE_PRECISION (type);
> + int_range<2> rel_range;
> + rel_range.set_varying (type);
> + rel_range.update_bitmask
> + (irange_bitmask (wi::zero (prec), wi::minus_one (prec) << 1));
> + lhs_range.intersect (rel_range);
> + return true;
> +}
> +
> // Check to see if the range of OP2 indicates anything about the relation
> // between LHS and OP1.
>
> @@ -5436,6 +5458,26 @@ range_relational_tests ()
> ASSERT_TRUE (code == VREL_LT);
> }
>
> +static void
> +range_op_plus_relation_tests ()
> +{
> + int_range_max result;
> + int_range<1> op1 (integer_type_node);
> + int_range<1> op2 (integer_type_node);
> +
> + relation_kind relations[] = {
> + VREL_EQ, VREL_PE8, VREL_PE16, VREL_PE32, VREL_PE64
> + };
> + for (relation_kind rel : relations)
> + {
> + op_plus.fold_range (result, integer_type_node, op1, op2,
> + relation_trio::op1_op2 (rel));
> + irange_bitmask bm = result.get_bitmask ();
> + ASSERT_FALSE (bm.member_p (INT (1)));
> + ASSERT_TRUE (bm.member_p (INT (2)));
> + }
> +}
> +
> void
> range_op_tests ()
> {
> @@ -5444,6 +5486,7 @@ range_op_tests ()
> range_op_bitwise_and_tests ();
> range_op_cast_tests ();
> range_relational_tests ();
> + range_op_plus_relation_tests ();
>
> extern void range_op_float_tests ();
> range_op_float_tests ();
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr126877-2.c
> b/gcc/testsuite/gcc.dg/tree-ssa/pr126877-2.c
> new file mode 100644
> index 00000000000..62b31bab82a
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/pr126877-2.c
> @@ -0,0 +1,15 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -ftrapv -fdump-tree-evrp" } */
> +
> +int
> +f (int a, int b)
> +{
> + if (a == b)
> + {
> + int c = a + b;
> + return c & 1;
> + }
> + return 1;
> +}
> +
> +/* { dg-final { scan-tree-dump-times " \\+ " 1 "evrp" } } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr126877.c
> b/gcc/testsuite/gcc.dg/tree-ssa/pr126877.c
> new file mode 100644
> index 00000000000..5ae06d75d6e
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/pr126877.c
> @@ -0,0 +1,18 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdisable-tree-dom1 -fdisable-tree-dom2
> -fdisable-tree-dom3 -fdump-tree-evrp" } */
> +
> +int
> +f (int a, int b)
> +{
> + if (a == b)
> + {
> + int c = a + b;
> + return c & 1;
> + }
> + return 1;
> +}
> +
> +/* The relation between distinct SSA names must be sufficient to make the
> + addition's low bit known zero. */
> +/* { dg-final { scan-tree-dump-not " \\+ " "evrp" } } */
> +/* { dg-final { scan-tree-dump-not " & 1" "evrp" } } */
> --
> 2.43.0
>