https://gcc.gnu.org/g:59c8c4bbac46570ac36782172ed28569affe42c4
commit r17-4549-g59c8c4bbac46570ac36782172ed28569affe42c4 Author: Naveen <[email protected]> Date: Mon Sep 21 20:56:16 2026 -0700 tree-optimization: fold abs(x) comparisons with x [PR112626] The patterns account for both undefined-overflow and wrapping-overflow semantics. Comparisons whose result is independent of the value of `x` are folded to constants while the remaining cases are reduced to sign tests. With wrapping overflow, the `INT_MIN` case is handled explicitly using unsigned comparisons. Handle comparisons between unsigned conversions of `abs (x)` and `x`. These transformations are applied only when the conversions have matching unsigned types and the same precision. Sign-test forms are restricted to undefined-overflow semantics; wrapping-overflow variants are left for future work. gcc ChangeLog: PR tree-optimization/112626 * match.pd: Fold comparisons between abs (x) and x including unsigned conversions of both operands. gcc/testsuite/ChangeLog: PR tree-optimization/112626 * gcc.dg/tree-ssa/pr112626.c: New test. * gcc.dg/tree-ssa/pr112626-wrap.c: New test. Signed-off-by: Naveen <[email protected]> Diff: --- gcc/match.pd | 72 +++++++++++++++++++++++++++ gcc/testsuite/gcc.dg/tree-ssa/pr112626-wrap.c | 33 ++++++++++++ gcc/testsuite/gcc.dg/tree-ssa/pr112626.c | 37 ++++++++++++++ 3 files changed, 142 insertions(+) diff --git a/gcc/match.pd b/gcc/match.pd index 268bf3f11e52..b6a9d4e79be2 100644 --- a/gcc/match.pd +++ b/gcc/match.pd @@ -8164,6 +8164,78 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT) wide_int_to_tree (itype, wi::mul (i, wi::to_wide (@2))); }))))) +/* abs (X) >= X -> true. */ +/* abs (X) < X -> false. */ +(for cmp (ge lt) + (simplify + (cmp:c (abs @0) @0) + (if (INTEGRAL_TYPE_P (TREE_TYPE (@0)) + && !TYPE_UNSIGNED (TREE_TYPE (@0)) + && !TYPE_OVERFLOW_TRAPS (TREE_TYPE (@0)) + && !TYPE_OVERFLOW_SANITIZED (TREE_TYPE (@0))) + { constant_boolean_node (cmp == GE_EXPR, type); }))) + +/* abs (X) > X -> X < 0. */ +/* abs (X) != X -> X < 0. */ +/* abs (X) <= X -> X >= 0. */ +/* abs (X) == X -> X >= 0. */ +/* When signed overflow wraps, abs (INT_MIN) == INT_MIN. */ +(for cmp (gt ne le eq) + out (lt lt ge ge) + (simplify + (cmp:c (abs @0) @0) + (if (INTEGRAL_TYPE_P (TREE_TYPE (@0)) + && !TYPE_UNSIGNED (TREE_TYPE (@0)) + && !TYPE_OVERFLOW_TRAPS (TREE_TYPE (@0)) + && !TYPE_OVERFLOW_SANITIZED (TREE_TYPE (@0))) + (switch + (if (TYPE_OVERFLOW_UNDEFINED (TREE_TYPE (@0))) + (out @0 { build_zero_cst (TREE_TYPE (@0)); })) + (if (TYPE_OVERFLOW_WRAPS (TREE_TYPE (@0))) + (with + { + tree utype = unsigned_type_for (TREE_TYPE (@0)); + wide_int min = wi::min_value (TREE_TYPE (@0)); + } + (if (cmp == LE_EXPR || cmp == EQ_EXPR) + (lt (convert:utype @0) { wide_int_to_tree (utype, min + 1); }) + (gt (convert:utype @0) { wide_int_to_tree (utype, min); })))))))) + +/* (unsigned) abs (X) <= (unsigned) X -> true. */ +/* (unsigned) abs (X) > (unsigned) X -> false. */ +(for cmp (le gt) + (simplify + (cmp:c (convert@1 (abs @0)) (convert@2 @0)) + (if (INTEGRAL_TYPE_P (TREE_TYPE (@0)) + && !TYPE_UNSIGNED (TREE_TYPE (@0)) + && INTEGRAL_TYPE_P (TREE_TYPE (@1)) + && TYPE_UNSIGNED (TREE_TYPE (@1)) + && types_match (TREE_TYPE (@1), TREE_TYPE (@2)) + && element_precision (TREE_TYPE (@0)) == element_precision (TREE_TYPE (@1)) + && !TYPE_OVERFLOW_TRAPS (TREE_TYPE (@0)) + && !TYPE_OVERFLOW_SANITIZED (TREE_TYPE (@0))) + { constant_boolean_node (cmp == LE_EXPR, type); }))) + +/* (unsigned) abs (X) < (unsigned) X -> X < 0. */ +/* (unsigned) abs (X) != (unsigned) X -> X < 0. */ +/* (unsigned) abs (X) >= (unsigned) X -> X >= 0. */ +/* (unsigned) abs (X) == (unsigned) X -> X >= 0. */ +/* TODO: Add wrapping-overflow variants for these four cases. */ +(for cmp (lt ne ge eq) + out (lt lt ge ge) + (simplify + (cmp:c (convert@1 (abs @0)) (convert@2 @0)) + (if (INTEGRAL_TYPE_P (TREE_TYPE (@0)) + && !TYPE_UNSIGNED (TREE_TYPE (@0)) + && INTEGRAL_TYPE_P (TREE_TYPE (@1)) + && TYPE_UNSIGNED (TREE_TYPE (@1)) + && types_match (TREE_TYPE (@1), TREE_TYPE (@2)) + && element_precision (TREE_TYPE (@0)) == element_precision (TREE_TYPE (@1)) + && !TYPE_OVERFLOW_TRAPS (TREE_TYPE (@0)) + && !TYPE_OVERFLOW_SANITIZED (TREE_TYPE (@0)) + && TYPE_OVERFLOW_UNDEFINED (TREE_TYPE (@0))) + (out @0 { build_zero_cst (TREE_TYPE (@0)); })))) + /* Simplify comparison of something with itself. For IEEE floating-point, we can only do some of these simplifications. */ (for cmp (eq ge le) diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr112626-wrap.c b/gcc/testsuite/gcc.dg/tree-ssa/pr112626-wrap.c new file mode 100644 index 000000000000..bdc0d17aa8a0 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/pr112626-wrap.c @@ -0,0 +1,33 @@ +/* PR tree-optimization/112626 - wrapping (-fwrapv) variant. */ +/* { dg-do compile } */ +/* { dg-options "-O2 -fwrapv -fdump-tree-optimized" } */ + +#define FUNC(NAME, OP) \ +int abs_##NAME (int a) { return __builtin_abs (a) OP a; } +#define FUNC_SWAPPED(NAME, OP) \ +int a_##NAME##_abs (int a) { return a OP __builtin_abs (a); } +#define FUNC_U(NAME, OP) \ +int abs_##NAME##_u (int a) { return ((unsigned) __builtin_abs (a)) OP (unsigned) a; } + +FUNC (le, <=) +FUNC (lt, <) +FUNC (ge, >=) +FUNC (gt, >) +FUNC (eq, ==) +FUNC (ne, !=) + +FUNC_SWAPPED (le, <=) +FUNC_SWAPPED (lt, <) +FUNC_SWAPPED (ge, >=) +FUNC_SWAPPED (gt, >) +FUNC_SWAPPED (eq, ==) +FUNC_SWAPPED (ne, !=) + +FUNC_U (le, <=) +FUNC_U (gt, >) + +/* { dg-final { scan-tree-dump-times "return 1;" 3 "optimized" } } */ +/* { dg-final { scan-tree-dump-times "return 0;" 3 "optimized" } } */ +/* { dg-final { scan-tree-dump-times " > 2147483648" 4 "optimized" } } */ +/* { dg-final { scan-tree-dump-times " <= 2147483648" 4 "optimized" } } */ +/* { dg-final { scan-tree-dump-not "ABS_EXPR" "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr112626.c b/gcc/testsuite/gcc.dg/tree-ssa/pr112626.c new file mode 100644 index 000000000000..428d491eff20 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/pr112626.c @@ -0,0 +1,37 @@ +/* PR tree-optimization/112626 */ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-optimized" } */ + +#define FUNC(NAME, OP) \ +int abs_##NAME (int a) { return __builtin_abs (a) OP a; } +#define FUNC_SWAPPED(NAME, OP) \ +int a_##NAME##_abs (int a) { return a OP __builtin_abs (a); } +#define FUNC_U(NAME, OP) \ +int abs_##NAME##_u (int a) { return ((unsigned) __builtin_abs (a)) OP (unsigned) a; } + +FUNC (le, <=) +FUNC (lt, <) +FUNC (ge, >=) +FUNC (gt, >) +FUNC (eq, ==) +FUNC (ne, !=) + +FUNC_SWAPPED (le, <=) +FUNC_SWAPPED (lt, <) +FUNC_SWAPPED (ge, >=) +FUNC_SWAPPED (gt, >) +FUNC_SWAPPED (eq, ==) +FUNC_SWAPPED (ne, !=) + +FUNC_U (le, <=) +FUNC_U (lt, <) +FUNC_U (ge, >=) +FUNC_U (gt, >) +FUNC_U (eq, ==) +FUNC_U (ne, !=) + +/* { dg-final { scan-tree-dump-times "return 1;" 3 "optimized" } } */ +/* { dg-final { scan-tree-dump-times "return 0;" 3 "optimized" } } */ +/* { dg-final { scan-tree-dump-times " < 0" 6 "optimized" } } */ +/* { dg-final { scan-tree-dump-times " >= 0" 6 "optimized" } } */ +/* { dg-final { scan-tree-dump-not "ABS_EXPR" "optimized" } } */
