On Tue, Aug 11, 2026 at 11:21 AM Eikansh Gupta
<[email protected]> wrote:
>
> 'switch (D + C)' with an integer constant C is equivalent to 'switch (D)'
> with every case label decreased by C.  Fold the offset into the labels so
> the addition is removed and the bare index is exposed to later passes; e.g.
> 'switch (v + 20)' now tests v against 150..157 instead of v + 20 against
> 170..177.
>
> Trapping (-ftrapv) and sanitized (-fsanitize=signed-integer-overflow) types
> are left alone, since removing the addition would drop the trap or diagnostic
> that must still happen when D + C overflows.
>
>         PR tree-optimization/121460
>
> gcc/ChangeLog:
>
>         * tree-ssa-forwprop.cc (simplify_gimple_switch_offset): New function.
>         (simplify_gimple_switch): Call simplify_gimple_switch_offset.
>
> gcc/testsuite/ChangeLog:
>
>         * gcc.dg/tree-ssa/switch-addfold-1.c: New test.
>         * gcc.dg/tree-ssa/switch-addfold-2.c: New test.
>         * gcc.dg/tree-ssa/switch-addfold-3.c: New test.
>
> Signed-off-by: Eikansh Gupta <[email protected]>
> ---
>  .../gcc.dg/tree-ssa/switch-addfold-1.c        | 61 +++++++++++++++++
>  .../gcc.dg/tree-ssa/switch-addfold-2.c        | 27 ++++++++
>  .../gcc.dg/tree-ssa/switch-addfold-3.c        | 34 ++++++++++
>  gcc/tree-ssa-forwprop.cc                      | 66 +++++++++++++++++++
>  4 files changed, 188 insertions(+)
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/switch-addfold-1.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/switch-addfold-2.c
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/switch-addfold-3.c
>
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/switch-addfold-1.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/switch-addfold-1.c
> new file mode 100644
> index 00000000000..ee7d9938ac0
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/switch-addfold-1.c
> @@ -0,0 +1,61 @@
> +/* PR tree-optimization/121460 */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-forwprop1" } */
> +
> +/* Verify that a constant offset on a switch index is folded into the
> +   case labels, so the addition/subtraction is removed and the switch
> +   dispatches on the bare index.  */
> +void foo (int, int);
> +
> +void add (unsigned char v, int w)
> +{
> +  int i, j;
> +  switch (v + 20)
> +    {
> +    case 170: i = 1; j = w; break;
> +    case 171: i = 2; j = v; break;
> +    case 172: i = 3; j = -19; break;
> +    case 173: i = 4; j = -w; break;
> +    case 174: i = 5; j = 55; break;
> +    case 175: i = 6; j = w; break;
> +    case 176: i = 7; j = 55; break;
> +    case 177: i = 8; j = 55; break;
> +    default: __builtin_unreachable ();
> +    }
> +  foo (i, j);
> +}
> +
> +void sub (int v)
> +{
> +  int i;
> +  switch (v - 5)
> +    {
> +    case 10: i = 1; break;
> +    case 11: i = 2; break;
> +    case 12: i = 3; break;
> +    default: i = 0; break;
> +    }
> +  foo (i, v);
> +}
> +
> +void range (int v)
> +{
> +  int i;
> +  switch (v + 100)
> +    {
> +    case 110 ... 115: i = 1; break;
> +    case 120: i = 2; break;
> +    default: i = 0; break;
> +    }
> +  foo (i, v);
> +}
> +
> +/* The additive offset should be gone from the switch index.  */
> +/* { dg-final { scan-tree-dump-not "\\+ 20" "forwprop1" } } */
> +/* { dg-final { scan-tree-dump-not "\\+ -5;" "forwprop1" } } */
> +/* { dg-final { scan-tree-dump-not "\\+ 100" "forwprop1" } } */
> +
> +/* Labels should be shifted down accordingly.  */
> +/* { dg-final { scan-tree-dump "case 150:" "forwprop1" } } */
> +/* { dg-final { scan-tree-dump "case 15:" "forwprop1" } } */
> +/* { dg-final { scan-tree-dump "case 10 ... 15:" "forwprop1" } } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/switch-addfold-2.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/switch-addfold-2.c
> new file mode 100644
> index 00000000000..91a31f32414
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/switch-addfold-2.c
> @@ -0,0 +1,27 @@
> +/* PR tree-optimization/121460 */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-forwprop1" } */
> +
> +void foo (int);
> +
> +/* GIMPLE canonicalizes 'un - 10' into 'un + (-10)', i.e. 'un + 4294967286',
> +   so the fold sees a large wrapped constant.  Subtracting it from each label
> +   wraps back to the intended value (10 - 4294967286 == 20 and
> +   20 - 4294967286 == 30, modulo 2**32), which is correct.  A plain overflow
> +   check would wrongly bail here (the subtraction underflows); the fold 
> instead
> +   only requires the shifted labels to stay sorted (20 <= 30), so it still
> +   applies.  */
> +void usub (unsigned un)
> +{
> +  switch (un - 10)
> +    {
> +    case 10: foo (1); break;   /* un == 20 */
> +    case 20: foo (2); break;   /* un == 30 */
> +    default: foo (0); break;
> +    }
> +}
> +
> +/* { dg-final { scan-tree-dump "switch \\(un" "forwprop1" } } */
> +/* { dg-final { scan-tree-dump "case 20:" "forwprop1" } } */
> +/* { dg-final { scan-tree-dump "case 30:" "forwprop1" } } */
> +/* { dg-final { scan-tree-dump-not "4294967286" "forwprop1" } } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/switch-addfold-3.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/switch-addfold-3.c
> new file mode 100644
> index 00000000000..fbc8f2fa690
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/switch-addfold-3.c
> @@ -0,0 +1,34 @@
> +/* PR tree-optimization/121460 */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-forwprop1" } */
> +
> +void foo (int);
> +
> +/* u + 1: labels would become UINT_MAX and 1 (reordered) -> bail.  */
> +void wrap (unsigned u)
> +{
> +  switch (u + 1)
> +    {
> +    case 0: foo (1); break;
> +    case 2: foo (2); break;
> +    default: foo (0); break;
> +    }
> +}
> +
> +/* Same, but the case range would itself wrap.  */
> +void wrap_range (unsigned u)
> +{
> +  switch (u + 1)
> +    {
> +    case 0 ... 2: foo (1); break;
> +    case 10: foo (2); break;
> +    default: foo (0); break;
> +    }
> +}
> +
> +/* Additions survive and labels stay unchanged; the wrapped value is absent. 
>  */
> +/* { dg-final { scan-tree-dump-times " \\+ 1;" 2 "forwprop1" } } */
> +/* { dg-final { scan-tree-dump "case 0:" "forwprop1" } } */
> +/* { dg-final { scan-tree-dump "case 0 ... 2:" "forwprop1" } } */
> +/* { dg-final { scan-tree-dump "case 10:" "forwprop1" } } */
> +/* { dg-final { scan-tree-dump-not "4294967295" "forwprop1" } } */
> diff --git a/gcc/tree-ssa-forwprop.cc b/gcc/tree-ssa-forwprop.cc
> index 75f06c6ba41..580c99ecb59 100644
> --- a/gcc/tree-ssa-forwprop.cc
> +++ b/gcc/tree-ssa-forwprop.cc
> @@ -1035,6 +1035,66 @@ simplify_gimple_switch_label_vec (gswitch *stmt, tree 
> index_type,
>      }
>  }
>
> +/* Helper for simplify_gimple_switch.  Try to fold a constant offset on the
> +   index of switch STMT into its case labels: 'switch (D + C)' is equivalent
> +   to 'switch (D)' with every case label decreased by C.  Return true
> +   if the rewrite was performed.  */
> +static bool
> +simplify_gimple_switch_offset (gswitch *stmt, tree cond, gimple *def_stmt,
> +                              bitmap simple_dce_worklist)
> +{
> +  if (!is_gimple_assign (def_stmt)
> +      || gimple_assign_rhs_code (def_stmt) != PLUS_EXPR)
> +    return false;
> +
> +  tree op0 = gimple_assign_rhs1 (def_stmt);
> +  tree op1 = gimple_assign_rhs2 (def_stmt);
> +  tree type = TREE_TYPE (cond);
> +  unsigned num_labels = gimple_switch_num_labels (stmt);
> +
> +  if (TREE_CODE (op0) != SSA_NAME
> +      || TREE_CODE (op1) != INTEGER_CST
> +      || integer_zerop (op1)
> +      || !INTEGRAL_TYPE_P (type)
> +      /* Removing the addition would drop a trap or sanitizer check for the
> +        inputs where D + C overflows, so leave those modes alone.  */
> +      || TYPE_OVERFLOW_TRAPS (type)
> +      || TYPE_OVERFLOW_SANITIZED (type))

I think you want to guard against a large unsigned constant with MSB set
so that the effective values become only smaller?  In fact, would it make
sense to canonicalize the minimum CASE_LOW to zero?  Or is the point
of the patch to elide the shift of the switch value?  Because for a casei
tablejump we'll shift the values again.  But smaller case values are
cheaper to materialize in case of a decision tree expansion.

> +    return false;
> +
> +  if (num_labels < 2)
> +    return false;
> +
> +  signop sgn = TYPE_SIGN (type);
> +  wide_int c = wi::to_wide (op1);
> +  tree last = gimple_switch_label (stmt, num_labels - 1);
> +  tree min = CASE_LOW (gimple_switch_label (stmt, 1));
> +  tree max = CASE_HIGH (last) ? CASE_HIGH (last) : CASE_LOW (last);
> +
> +  /* Check if the shifted extremes stay in order.  This will ensure that all
> +     the labels are in order after shifting them.  */
> +  if (!wi::le_p (wi::sub (wi::to_wide (min), c),
> +                wi::sub (wi::to_wide (max), c), sgn))
> +    return false;
> +
> +  /* Shift every label in place and switch to the bare index.  */
> +  for (unsigned i = 1; i < num_labels; i++)
> +    {
> +      tree label = gimple_switch_label (stmt, i);
> +      CASE_LOW (label)
> +       = wide_int_to_tree (type, wi::sub (wi::to_wide (CASE_LOW (label)), 
> c));
> +      if (CASE_HIGH (label))
> +       CASE_HIGH (label)
> +         = wide_int_to_tree (type,
> +                             wi::sub (wi::to_wide (CASE_HIGH (label)), c));
> +    }
> +
> +  bitmap_set_bit (simple_dce_worklist, SSA_NAME_VERSION (cond));
> +  gimple_switch_set_index (stmt, op0);
> +  update_stmt (stmt);
> +  return true;
> +}
> +
>  /* STMT is a SWITCH_EXPR for which we attempt to find equivalent forms of
>     the condition which we may be able to optimize better.  */
>
> @@ -1086,6 +1146,12 @@ simplify_gimple_switch (gswitch *stmt,
>                 }
>             }
>         }
> +
> +      /* Otherwise try to fold a constant additive offset on the index
> +        into the case labels.  */
> +      if (simplify_gimple_switch_offset (stmt, cond, def_stmt,
> +                                        simple_dce_worklist))
> +       return true;
>      }
>
>    return false;
> --
> 2.34.1
>

Reply via email to