https://gcc.gnu.org/g:69917a513ed98081093780505cf33122315b75c4

commit 69917a513ed98081093780505cf33122315b75c4
Author: David Balek <[email protected]>
Date:   Wed Sep 17 16:36:52 2025 +0200

    adds the buggy new pass

Diff:
---
 gcc/Makefile.in              |   1 +
 gcc/gimple-flatten-switch.cc | 789 +++++++++++++++++++++++++++++++++++++++++++
 gcc/passes.def               |   1 +
 gcc/timevar.def              |   1 +
 gcc/tree-pass.h              |   1 +
 5 files changed, 793 insertions(+)

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index f21d69298743..20e8fd10f0b3 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1494,6 +1494,7 @@ OBJS = \
        gimple-array-bounds.o \
        gimple-builder.o \
        gimple-expr.o \
+        gimple-flatten-switch.o \
        gimple-if-to-switch.o \
        gimple-iterator.o \
        gimple-fold.o \
diff --git a/gcc/gimple-flatten-switch.cc b/gcc/gimple-flatten-switch.cc
new file mode 100644
index 000000000000..eedf05e005a8
--- /dev/null
+++ b/gcc/gimple-flatten-switch.cc
@@ -0,0 +1,789 @@
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "rtl.h"
+#include "tree.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-pretty-print.h"
+#include "fold-const.h"
+#include "gimple-iterator.h"
+#include "tree-cfg.h"
+#include "tree-dfa.h"
+#include "tree-cfgcleanup.h"
+#include "alias.h"
+#include "tree-ssa-loop.h"
+#include "diagnostic.h"
+#include "cfghooks.h"
+#include "tree-into-ssa.h"
+#include "cfganal.h"
+#include "dbgcnt.h"
+#include "target.h"
+#include "alloc-pool.h"
+#include "tree-switch-conversion.h"
+#include "tree-ssa-reassoc.h"
+#include "tree-ssa.h"
+
+
+/* The struct holding the info about outer switch and all inner switches it
+   points to. */
+struct switch_info
+{
+  switch_info(gswitch *outer_switch, gswitch *inner_switch) :
+  m_outer_switch(outer_switch), m_inner_switches()
+  {
+    m_inner_switches.create (1);
+    m_inner_switches.safe_push (inner_switch);
+  }
+
+  void add_inner_switch (gswitch *inner_switch);
+  gswitch *m_outer_switch;
+  auto_vec<gswitch*> m_inner_switches;
+};
+
+void
+switch_info::add_inner_switch (gswitch *inner_switch)
+{
+  m_inner_switches.safe_push(inner_switch);
+}
+
+void
+print_nested_switches (basic_block bb, gswitch *sw1, gswitch *sw2)
+{
+  if (!dump_file)
+    return;
+  fprintf(dump_file, "Found nested switch in bb number %d:\n", bb->index);
+  print_gimple_stmt(dump_file, sw1, 0, TDF_DETAILS);
+  fprintf(dump_file, "- Index var is: ");
+  print_generic_expr(dump_file, gimple_switch_index(sw1), TDF_DETAILS);
+  for (size_t i = 0; i < gimple_switch_num_labels(sw1); ++i)
+  {
+    fprintf(dump_file, "\n * Label number %ld -> label is: ", i);
+    print_generic_expr(dump_file, gimple_switch_label(sw1, i), TDF_DETAILS);
+  }
+  fprintf(dump_file, "\n !!! NESTED SWITCH !!!\n");
+  fprintf(dump_file, "The nested switch is in the bb number %d\n", 
sw2->bb->index);
+  print_gimple_stmt(dump_file, sw2, 0, TDF_DETAILS);
+  fprintf(dump_file, "- Index var is: ");
+  print_generic_expr(dump_file, gimple_switch_index(sw2), TDF_DETAILS);
+  for (size_t i = 0; i < gimple_switch_num_labels(sw2); ++i)
+  {
+    fprintf(dump_file, "\n * Label number %ld -> label is: ", i);
+    print_generic_expr(dump_file, gimple_switch_label(sw2, i), TDF_DETAILS);
+  }
+  fprintf(dump_file, "\n---------------\n\n");
+}
+
+/* This struct keeps the information about case label of some switch */
+struct case_info
+{
+  using mapping_vec = auto_vec<std::pair<gphi *, tree>>;
+
+  case_info (tree low, tree high, tree label, basic_block this_bb,
+             basic_block dest_bb, bool is_inner, bool points_to_nested) :
+    m_low (low), m_high (high), m_label (label), m_this_bb (this_bb),
+    m_dest_bb (dest_bb), m_is_inner (is_inner),
+    m_points_to_nested (points_to_nested), m_phi_mapping (),
+    m_forwarder_bb (NULL)
+  {
+    m_phi_mapping.create (0);
+  }
+
+  case_info(const case_info& other) :
+    m_low (other.m_low), m_high (other.m_high), m_label (other.m_label),
+    m_this_bb (other.m_this_bb), m_dest_bb (other.m_dest_bb),
+    m_is_inner (other.m_is_inner),
+    m_points_to_nested (other.m_points_to_nested), m_phi_mapping (),
+    m_forwarder_bb (other.m_forwarder_bb)
+  {
+    m_phi_mapping.create (other.m_phi_mapping.length());
+    for (unsigned i = 0; i < other.m_phi_mapping.length(); ++i)
+      m_phi_mapping.safe_push(other.m_phi_mapping[i]);
+  }
+
+  /* Creates case label to be added to gimple switch statement from
+    this struct */
+  tree build_case ();
+  /* Checks if the case label is default */
+  bool is_default ();
+  /* Checks if 2 case ranges have empty intersection */
+  bool is_intersection_empty (case_info *other);
+  /* Checks if case range of this struct is a subset of other case range */
+  bool is_subset_of (case_info *other);
+  /* Checks if 2 ranges are equal*/
+  bool is_range_equal (case_info *other);
+  /* Checks if 2 ranges have the same lower bound */
+  bool share_lower_bound (case_info *other);
+  /* Checks if 2 ranges have the same upper bound */
+  bool share_upper_bound (case_info *other);
+  /* Recond PHI mapping for an original edge E from switch statement to case
+    label dest and save these into vector m_phi_mapping.  */
+  void record_phi_mapping ();
+  /* Makes this case point to the same label as the other case */
+  void point_to_as (case_info *other);
+
+  tree m_low; /* lower bound of case range */
+  tree m_high; /* upper bound of case range */
+  tree m_label; /* destination label of case */
+  basic_block m_this_bb; /* case location bb */
+  basic_block m_dest_bb; /* destination label location bb */
+  bool m_is_inner; /* case belonging to inner switch */
+  bool m_points_to_nested; /* case pointing to inner switch */
+  mapping_vec m_phi_mapping;
+  basic_block m_forwarder_bb;
+};
+
+/* Creates case label to be added to gimple switch statement from this struct 
*/
+tree
+case_info::build_case ()
+{
+  tree label = (m_forwarder_bb == NULL)
+               ? m_label : gimple_block_label (m_forwarder_bb);
+  return build_case_label (m_low,
+                           m_low == m_high ? NULL_TREE : m_high,
+                           label);
+}
+
+/* Checks if the case label is default */
+bool
+case_info::is_default ()
+{
+  return m_low == NULL_TREE && m_high == NULL_TREE;
+}
+
+
+/* Checks if 2 case ranges have empty intersection */
+bool
+case_info::is_intersection_empty (case_info *other)
+{
+  return (tree_int_cst_lt (m_high, other->m_low)
+          || tree_int_cst_lt (other->m_high, m_low));
+}
+
+/* Checks if case range of this struct is a subset of other case range */
+bool
+case_info::is_subset_of (case_info *other)
+{
+  return (tree_int_cst_le (other->m_low, m_low)
+          && tree_int_cst_le (m_high, other->m_high));
+}
+
+/* Checks if 2 ranges are equal*/
+bool
+case_info::is_range_equal (case_info *other)
+{
+  return (tree_int_cst_equal (m_low, other->m_low)
+          && tree_int_cst_equal (m_high, other->m_high));
+}
+
+/* Checks if 2 ranges have the same lower bound */
+bool
+case_info::share_lower_bound (case_info *other)
+{
+  return tree_int_cst_equal (m_low, other->m_low);
+}
+
+/* Checks if 2 ranges have the same upper bound */
+bool
+case_info::share_upper_bound (case_info *other)
+{
+  return tree_int_cst_equal (m_high, other->m_high);
+}
+
+/* Makes this case point to the same label as the other case */
+void
+case_info::point_to_as (case_info *other)
+{
+  m_label = other->m_label;
+  m_dest_bb = other->m_dest_bb;
+  m_is_inner = other->m_is_inner;
+  m_points_to_nested = other->m_points_to_nested;
+}
+
+/* Recond PHI mapping for an original edge E from switch statement to case 
label dest
+   and save these into vector m_phi_mapping.  */
+void
+case_info::record_phi_mapping ()
+{
+  edge e = find_edge (m_this_bb, m_dest_bb);
+  if (e == NULL)
+    return;
+
+  for (gphi_iterator gsi = gsi_start_phis (e->dest); !gsi_end_p (gsi);
+       gsi_next (&gsi))
+    {
+      gphi *phi = gsi.phi ();
+      tree arg = PHI_ARG_DEF_FROM_EDGE (phi, e);
+      m_phi_mapping.safe_push (std::make_pair (phi, arg));
+    }
+}
+
+using case_informations = auto_vec<case_info*>;
+
+void print_merged_switch (case_informations &merged_infos)
+{
+  if (!dump_file)
+    return;
+  fprintf(dump_file, "\n\nMERGED SWITCH WOULD LOOK LIKE THIS:");
+  int n = 0;
+  for (auto info : merged_infos)
+  {
+    tree case_label = info->build_case();
+    fprintf(dump_file, "\n * Label number %d -> label is: ", n);
+    print_generic_expr(dump_file, case_label, TDF_DETAILS);
+    ++n;
+  }
+}
+
+void print_inner_outer (case_info *outer, case_info *inner)
+{
+  if (!dump_file)
+    return;
+  if (outer == NULL)
+    fprintf(dump_file, "\n Outer label is: NULL");
+  else
+    {
+      fprintf(dump_file, "\n Outer label is: ");
+      print_generic_expr(dump_file, outer->build_case(), TDF_DETAILS);
+    }
+  fprintf(dump_file, "\n Inner label is: ");
+  print_generic_expr(dump_file, inner->build_case(), TDF_DETAILS);
+  fprintf(dump_file, "\n ---------- ");
+}
+
+
+/* Compare 2 case label ranges by lower bound, default label always compares
+  as lower */
+static int
+case_info_cmp (const void *a, const void *b)
+{
+  const case_info *ci1 = *(const case_info * const *) a;
+  const case_info *ci2 = *(const case_info * const *) b;
+  if (ci1->m_low == NULL_TREE && ci2->m_low == NULL_TREE)
+    return 0;
+  if (ci1->m_low == NULL_TREE)
+    return -1;
+  if (ci2->m_low == NULL_TREE)
+    return 1;
+  return tree_int_cst_compare (ci1->m_low, ci2->m_low);
+}
+
+/* Create struct case_info */
+static case_info*
+get_case_info (function* fun, basic_block this_bb, tree case_label,
+               bool is_inner, bool points_to_nested)
+{
+  tree low  = CASE_LOW (case_label);
+  tree high = CASE_HIGH (case_label);
+  tree dest = CASE_LABEL (case_label);
+  basic_block dest_bb = label_to_block (fun, dest);
+
+  return new case_info (low, (high == NULL_TREE) ? low : high,
+                        dest, this_bb, dest_bb, is_inner, points_to_nested);
+}
+
+/* Fills case_infos vector with structs created from inner switch labels */
+static void
+inner_switch_case_informations (function* fun, const gswitch* inner_switch,
+                                case_informations &case_infos)
+{
+  unsigned n = gimple_switch_num_labels (inner_switch);
+  basic_block bb = gimple_bb (inner_switch);
+  for (unsigned i = 0; i < n; ++i)
+    {
+      tree label = gimple_switch_label (inner_switch, i);
+      case_info* info = get_case_info (fun, bb, label, true, false);
+      case_infos.safe_push (info);
+    }
+}
+
+/* Fills case_infos vector with structs created from outer switch labels */
+static void
+outer_switch_case_informations (function *fun, const gswitch *outer_switch,
+                                const gswitch *inner_switch,
+                                case_informations &case_infos)
+{
+  basic_block inner_switch_bb = gimple_bb (inner_switch);
+  basic_block outer_switch_bb = gimple_bb (outer_switch);
+  unsigned n = gimple_switch_num_labels (outer_switch);
+  for (unsigned i = 0; i < n; ++i)
+    {
+      tree label = gimple_switch_label (outer_switch, i);
+      tree dest  = CASE_LABEL (label);
+      bool points_to_inner = (label_to_block (fun, dest) == inner_switch_bb);
+      case_info *info = get_case_info (fun, outer_switch_bb, label,
+                                       false, points_to_inner);
+      case_infos.safe_push (info);
+    }
+}
+
+
+/*
+Checks if the nested inner switch can be merged with the outer switch:
+- If the default label points to inner nested switch then in order to merge
+  succesfully the intersection of ranges not pointing to the inner nested 
switch
+  with inner switch nondefault label ranges must be empty.
+- If the default label doesn't point to inner nested switch then in order
+  to merge succesfully each inner nondefault range must be a subset of some
+  outer switch label range pointing to the inner switch.
+*/
+static bool
+check_if_switches_mergeable (case_informations &outer, case_informations 
&inner)
+{
+  bool outer_default_points_to_inner = outer[0]->m_points_to_nested;
+  for (auto inner_info : inner)
+    {
+
+      if (inner_info->is_default ())
+        continue;
+
+      if (outer_default_points_to_inner)
+        {
+          for (auto outer_info : outer)
+            {
+              if (outer_info->m_points_to_nested)
+                continue;
+
+              bool empty = inner_info->is_intersection_empty (outer_info);
+              if (!empty)
+                return false;
+            }
+        }
+      else
+        {
+          for (auto inner_info : inner)
+            {
+              if (inner_info->is_default ())
+                continue;
+
+              bool is_subset = false;
+              for (auto outer_info : outer)
+                {
+                  if (!outer_info->m_points_to_nested)
+                    continue;
+
+                  is_subset |= inner_info->is_subset_of (outer_info);
+                }
+
+              if (!is_subset)
+                return false;
+            }
+        }
+
+    }
+
+  return true;
+}
+
+
+
+/* Adds 1 to int constant */
+tree
+int_cst_plus_one (tree cst)
+{
+  gcc_assert (TREE_CODE (cst) == INTEGER_CST);
+  return int_const_binop (PLUS_EXPR, cst, build_int_cst (TREE_TYPE (cst), 1));
+}
+
+/* Subtracts 1 from int constant */
+tree
+int_cst_minus_one (tree cst)
+{
+  gcc_assert (TREE_CODE (cst) == INTEGER_CST);
+  return int_const_binop (PLUS_EXPR, cst, build_int_cst (TREE_TYPE (cst), -1));
+}
+
+
+/* This cuts out inner range from outer, adds left result to merged case infos
+   and returns the rest to be processed later. */
+case_info*
+subtract_ranges_and_merge (case_info *outer, case_info *inner,
+                           case_info *inner_default,
+                           case_informations &merged_case_infos)
+{
+  print_inner_outer(outer, inner);
+
+  if (outer == NULL)
+    return NULL;
+
+  /* If either of cases is default we don't cut anything */
+  if (outer->is_default () || inner->is_default ())
+    return outer;
+
+  /* Same if the ranges don't intersect */
+  if (outer->is_intersection_empty (inner))
+    return outer;
+
+
+  /* If ranges are equal we add inner case to merged cases and nothing is
+    left to process in next iterations, we return NULL */
+  if (outer->is_range_equal (inner))
+    {
+      merged_case_infos.safe_push (inner);
+      return NULL;
+    }
+
+  /* If ranges share lower bound we cut out inner from outer, add it to
+    merged cases and return right side to process later:
+    - outer=[0,4], inner=[0,1] -> we have to process later [2,4] */
+  if (outer->share_lower_bound (inner))
+    {
+      merged_case_infos.safe_push (inner);
+      outer->m_low = int_cst_plus_one (inner->m_high);
+      return outer;
+    }
+
+  /* If ranges share upper bound we cut out inner from outer and nothing is
+    left to process:
+    - outer=[0,4], inner=[3,4] -> new cases are [0,2] and [3,4] */
+  if (outer->share_upper_bound (inner))
+    {
+      outer->m_high = int_cst_minus_one (inner->m_low);
+      outer->point_to_as (inner_default);
+      merged_case_infos.safe_push (outer);
+      merged_case_infos.safe_push (inner);
+      return NULL;
+    }
+
+  /* If inner is subset of outer and share no bounds we cut out inner,
+    add left side to merged cases and return right side:
+    - outer=[0,5], inner=[2,3] -> new cases are [0,1] and [2,3], we process
+      later [4,5] */
+  case_info *copy_outer = new case_info (*outer);
+  copy_outer->m_high = int_cst_minus_one (inner->m_low);
+  copy_outer->point_to_as (inner_default);
+  outer->m_low = int_cst_plus_one (inner->m_high);
+  merged_case_infos.safe_push (copy_outer);
+  merged_case_infos.safe_push (inner);
+  return outer;
+}
+
+/* Merges case infos from inner and outer switch when outer default doesn't
+  point to inner switch, i.e.:
+  - every outer case label not pointing to inner switch should be in
+    merged switch
+  - for outer cases pointing to inner switch we iterate over sorted inner
+    cases and cut the inner ranges from outer, always keeping rightmost side
+    of remaining outer range to be compared with next larger inner ranges.
+    */
+static void
+get_merged_case_infos_def_not_to_inner (const case_informations &outer_infos,
+                                        const case_informations &inner_infos,
+                                        case_informations &merged_case_infos)
+{
+  case_info *inner_default = inner_infos[0];
+  for (auto outer_info : outer_infos)
+    {
+      if (!outer_info->m_points_to_nested)
+        {
+          merged_case_infos.safe_push (outer_info);
+          continue;
+        }
+
+      case_info *remaining_outer_info = outer_info;
+      for (auto inner_info : inner_infos)
+        {
+          remaining_outer_info = subtract_ranges_and_merge (
+                remaining_outer_info, inner_info, inner_default,
+                merged_case_infos);
+        }
+      if (remaining_outer_info != NULL)
+        {
+          remaining_outer_info->point_to_as (inner_default);
+          merged_case_infos.safe_push (remaining_outer_info);
+        }
+
+    }
+}
+
+/* Merges case infos from inner and outer switch when outer default points
+   to inner switch, i.e.:
+   - every inner case label should be in merged switch
+   - every outer case label not pointing to inner switch should be in
+     merged switch */
+static void
+get_merged_case_infos_def_to_inner (const case_informations &outer_infos,
+                                    const case_informations &inner_infos,
+                                    case_informations &merged_case_infos)
+{
+  for (auto outer_info : outer_infos)
+    {
+      if (outer_info->m_points_to_nested)
+        continue;
+      merged_case_infos.safe_push (outer_info);
+    }
+
+  for (auto inner_info : inner_infos)
+    {
+      merged_case_infos.safe_push (inner_info);
+    }
+}
+
+
+/* Merges the case informations for inner and outer switch */
+static void
+merge_case_infos (case_informations &outer_infos,
+                  case_informations &inner_infos,
+                  case_informations &merged_case_infos)
+{
+  bool outer_default_points_to_inner = outer_infos[0]->m_points_to_nested;
+  outer_infos.qsort (case_info_cmp);
+  inner_infos.qsort (case_info_cmp);
+
+  if (outer_default_points_to_inner)
+    get_merged_case_infos_def_to_inner (outer_infos, inner_infos,
+                                       merged_case_infos);
+  else
+    get_merged_case_infos_def_not_to_inner (outer_infos, inner_infos,
+                                       merged_case_infos);
+
+  merged_case_infos.qsort (case_info_cmp);
+  if (dump_file)
+    print_merged_switch(merged_case_infos);
+}
+
+/* Repairs the cfg, makes new edges from merged switch, possibly adding
+  forwarder blocks and adds the new PHI args where needed */
+static void
+repair_cfg (case_informations &merged_case_infos, basic_block merged_switch_bb)
+{
+  /* Find the bbs poited to by original outer switch cases and record any phi
+     statements where the case labels of both switches point to */
+  hash_set<basic_block> outer_switch_points_to;
+  for (auto info : merged_case_infos)
+    {
+      if (!info->m_is_inner)
+        outer_switch_points_to.add (info->m_dest_bb);
+      info->record_phi_mapping ();
+    }
+
+  /* Make the new edges for merged cases and possibly add forwarder block */
+  for (auto info : merged_case_infos)
+    {
+      info->m_this_bb = merged_switch_bb;
+      edge e = make_edge (info->m_this_bb, info->m_dest_bb, 0);
+
+      if (e == NULL)
+        e = find_edge(info->m_this_bb, info->m_dest_bb);
+
+      if (!info->m_is_inner)
+        continue;
+      if (!outer_switch_points_to.contains (info->m_dest_bb))
+        continue;
+      if (info->m_phi_mapping.is_empty ())
+        continue;
+
+      info->m_forwarder_bb = split_edge (e);
+      make_edge (info->m_this_bb, info->m_dest_bb, 0);
+    }
+
+    /* Add the missing phi arguments */
+    for (auto info : merged_case_infos)
+      {
+        edge e = (info->m_forwarder_bb == NULL)
+                 ? find_edge(info->m_this_bb, info->m_dest_bb)
+                 : find_edge(info->m_forwarder_bb, info->m_dest_bb);
+
+        for (auto item : info->m_phi_mapping)
+          {
+            add_phi_arg (item.first, item.second, e, UNKNOWN_LOCATION);
+          }
+      }
+}
+
+
+/* Adjusts the outer switch to be merged switch */
+static void
+change_outer_switch_to_merged_switch (gswitch *outer, case_informations 
&merged_case_infos)
+{
+  gcc_assert (merged_case_infos.length () > 0);
+  gcc_assert (merged_case_infos[0]->is_default ());
+  gimple_switch_set_num_labels (outer, merged_case_infos.length ());
+  for (unsigned i = 0; i < merged_case_infos.length (); ++i)
+    {
+      case_info *info = merged_case_infos[i];
+      tree label = info->build_case ();
+      gimple_switch_set_label (outer, i, label);
+    }
+}
+
+/* Merges nested switches */
+static void
+merge_nested_switches (function *fun, switch_info *info)
+{
+  gswitch* outer = info->m_outer_switch;
+  for (gswitch* inner : info->m_inner_switches)
+    {
+      case_informations outer_case_info{}, inner_case_info{};
+      outer_switch_case_informations (fun, outer, inner, outer_case_info);
+      inner_switch_case_informations (fun, inner, inner_case_info);
+
+      if (!check_if_switches_mergeable (outer_case_info, inner_case_info))
+        continue;
+
+      case_informations merged_case_info{};
+      dump_function_to_file (fun->decl, stderr, TDF_DETAILS);
+      merge_case_infos (outer_case_info, inner_case_info, merged_case_info);
+      repair_cfg (merged_case_info, outer->bb);
+      basic_block inner_bb = gimple_bb (inner);
+      basic_block outer_bb = gimple_bb (outer);
+      remove_edge (find_edge (outer_bb, inner_bb));
+      change_outer_switch_to_merged_switch (outer, merged_case_info);
+      delete_basic_block (inner->bb);
+    }
+}
+
+
+/* Checks if the bb contains only debug stmts, labels and switches */
+static bool
+bb_only_labels_and_switch (basic_block bb)
+{
+  for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+       !gsi_end_p (gsi);
+       gsi_next (&gsi))
+    {
+      gimple *stmt = gsi_stmt (gsi);
+
+      if (is_gimple_debug (stmt))
+        continue;
+
+      if (gimple_code (stmt) != GIMPLE_SWITCH
+          && gimple_code (stmt) != GIMPLE_LABEL)
+        return false;
+    }
+  return true;
+}
+
+/* Finds the pairs of outer and inner switches */
+static void
+find_nested_switches (hash_map<basic_block, switch_info *> *switch_in_bb,
+                      basic_block bb)
+{
+  gimple_stmt_iterator gsi = gsi_last_nondebug_bb(bb);
+  if (gsi_end_p(gsi))
+    return;
+
+  gswitch *outer_switch = dyn_cast<gswitch *>(gsi_stmt(gsi));
+  if (outer_switch == NULL)
+    return;
+
+  edge e;
+  edge_iterator ei;
+  FOR_EACH_EDGE (e, ei, bb->succs)
+    {
+      basic_block dest = e->dest;
+      if (!bb_only_labels_and_switch (dest) )
+        continue;
+
+      if (dest->preds->length() != 1)
+        continue;
+
+      gimple_stmt_iterator gsi_dest = gsi_last_nondebug_bb(dest);
+      if (gsi_end_p(gsi_dest))
+        continue;
+
+
+      gswitch *inner_switch = dyn_cast<gswitch *>(gsi_stmt(gsi_dest));
+      if (inner_switch == NULL)
+        continue;
+
+      tree idx_outer = gimple_switch_index(outer_switch);
+      tree idx_inner = gimple_switch_index(inner_switch);
+      if (idx_inner != idx_outer)
+        continue;
+
+      print_nested_switches(bb, outer_switch, inner_switch);
+
+      switch_info** info = switch_in_bb->get(bb);
+      if (info == NULL)
+        {
+          switch_info *new_info = new switch_info (outer_switch, inner_switch);
+          switch_in_bb->put(bb, new_info);
+          continue;
+        }
+      (*info)->add_inner_switch(inner_switch);
+    }
+}
+
+namespace
+{
+  const pass_data pass_data_flatten_switch =
+  {
+    GIMPLE_PASS,          /* type */
+    "flattenswitch",      /* name */
+    OPTGROUP_NONE,        /* optinfo_flags */
+    TV_TREE_FLATTEN_SWITCH,  /* tv_id */
+    ( PROP_cfg | PROP_ssa ), /* properties_required */
+    0,                    /* properties_provided */
+    0,                    /* properties_destroyed */
+    0,                    /* todo_flags_start */
+    ( TODO_update_ssa | TODO_cleanup_cfg )      /* todo_flags_finish */
+  };
+
+  class pass_flatten_switch : public gimple_opt_pass
+  {
+  public:
+    pass_flatten_switch(gcc::context *ctxt)
+        : gimple_opt_pass(pass_data_flatten_switch, ctxt)
+    {
+    }
+
+    unsigned int execute(function *) final override;
+
+  }; // class pass_flatten_switch
+
+  unsigned int
+  pass_flatten_switch::execute(function *fun)
+  {
+    hash_map<basic_block, switch_info *> switches_in_bbs;
+
+    basic_block bb;
+    FOR_EACH_BB_FN (bb, fun)
+      find_nested_switches (&switches_in_bbs, bb);
+
+
+    if (switches_in_bbs.is_empty ())
+      return 0;
+
+    int *rpo = XNEWVEC (int, n_basic_blocks_for_fn (fun));
+
+    unsigned n = pre_and_rev_post_order_compute_fn (fun, NULL, rpo, false);
+
+    auto_bitmap seen_bbs;
+    for (int i = n - 1; i >= 0; --i)
+      {
+        basic_block bb = BASIC_BLOCK_FOR_FN (fun, rpo[i]);
+        if (bitmap_bit_p (seen_bbs, bb->index))
+          continue;
+
+        bitmap_set_bit (seen_bbs, bb->index);
+        switch_info **info = switches_in_bbs.get (bb);
+        if (info == NULL)
+          continue;
+        merge_nested_switches(fun, *info);
+      }
+
+
+    free (rpo);
+
+
+    for (hash_map<basic_block, switch_info *>::iterator it
+        = switches_in_bbs.begin (); it != switches_in_bbs.end (); ++it)
+      delete (*it).second;
+
+    free_dominance_info (CDI_DOMINATORS);
+    dump_function_to_file (fun->decl, stderr, TDF_DETAILS);
+    return 0;
+
+
+  }
+
+} // anon namespace
+
+gimple_opt_pass *
+make_pass_flatten_switch(gcc::context *ctxt)
+{
+  return new pass_flatten_switch(ctxt);
+}
diff --git a/gcc/passes.def b/gcc/passes.def
index 68ce53baa0f1..e7aff5d38461 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -99,6 +99,7 @@ along with GCC; see the file COPYING3.  If not see
          NEXT_PASS (pass_phiopt, true /* early_p */);
          NEXT_PASS (pass_tail_recursion);
          NEXT_PASS (pass_if_to_switch);
+         NEXT_PASS (pass_flatten_switch);
          NEXT_PASS (pass_convert_switch);
          NEXT_PASS (pass_cleanup_eh);
          NEXT_PASS (pass_sccopy);
diff --git a/gcc/timevar.def b/gcc/timevar.def
index 4f60f04baa11..bd54a31b8e17 100644
--- a/gcc/timevar.def
+++ b/gcc/timevar.def
@@ -307,6 +307,7 @@ DEFTIMEVAR (TV_VAR_TRACKING_DATAFLOW , "var-tracking 
dataflow")
 DEFTIMEVAR (TV_VAR_TRACKING_EMIT     , "var-tracking emit")
 DEFTIMEVAR (TV_TREE_IFCOMBINE        , "tree if-combine")
 DEFTIMEVAR (TV_TREE_IF_TO_SWITCH     , "if to switch conversion")
+DEFTIMEVAR (TV_TREE_FLATTEN_SWITCH   , "merges nested switches")
 DEFTIMEVAR (TV_TREE_UNINIT           , "uninit var analysis")
 DEFTIMEVAR (TV_PLUGIN_INIT           , "plugin initialization")
 DEFTIMEVAR (TV_PLUGIN_RUN            , "plugin execution")
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index 1c68a69350df..f60751e0ac4a 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -388,6 +388,7 @@ extern gimple_opt_pass *make_pass_graphite (gcc::context 
*ctxt);
 extern gimple_opt_pass *make_pass_graphite_transforms (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_if_conversion (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_if_to_switch (gcc::context *ctxt);
+extern gimple_opt_pass *make_pass_flatten_switch (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_loop_distribution (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_crc_optimization (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_vectorize (gcc::context *ctxt);

Reply via email to