Add GIMPLE switch support to loop unswitching

Message ID 20220525083609.8C28713ADF@imap2.suse-dmz.suse.de
State New
Headers
Series Add GIMPLE switch support to loop unswitching |

Commit Message

Richard Biener May 25, 2022, 8:36 a.m. UTC
  This patch adds support to unswitch loops with switch statements
based on invariant index.  It furthermore reworks the cost model
to allow an overall budget of statements to be created per original
loop by all unswitching opportunities in the loop.  Compared to
the original all unswitching opportunities in a loop are
pre-evaluated before the first transform which will allow future
changes to select the most profitable candidates first.

To efficiently support switch statements the pass now uses
ranger to simplify switch statements and conditions in loop
copies based on ranges extracted from the recorded set of
predicates unswitched.

Bootstrapped and tested on x86_64-unknown-linux-gnu, pushed to trunk.

Richard.

gcc/ChangeLog:

	* dbgcnt.def (DEBUG_COUNTER): Add loop_unswitch counter.
	* params.opt (max-unswitch-level): Remove.
	* doc/invoke.texi (max-unswitch-level): Likewise.
	* tree-cfg.c (gimple_lv_add_condition_to_bb): Support not
	gimplified expressions.
	* tree-ssa-loop-unswitch.c (struct unswitch_predicate): New.
	(tree_may_unswitch_on): Rename to ...
	(find_unswitching_predicates_for_bb): ... this and handle
	switch statements.
	(get_predicates_for_bb): Likewise.
	(set_predicates_for_bb): Likewise.
	(init_loop_unswitch_info): Likewise.
	(tree_ssa_unswitch_loops): Prepare stuff before calling
	tree_unswitch_single_loop.
	(tree_unswitch_single_loop): Rework the function using
	pre-computed predicates and with a per original loop cost model.
	(merge_last): New.
	(add_predicate_to_path): Likewise.
	(find_range_for_lhs): Likewise.
	(simplify_using_entry_checks): Rename to ...
	(evaluate_control_stmt_using_entry_checks): ... this, handle
	switch statements and improve simplifications using ranger.
	(simplify_loop_version): Rework using
	evaluate_control_stmt_using_entry_checks.
	(evaluate_bbs): New.
	(evaluate_loop_insns_for_predicate): Likewise.
	(tree_unswitch_loop): Adjust to allow switch statements and
	pass in the edge to unswitch.
	(clean_up_after_unswitching): New.
	(pass_tree_unswitch::execute): Pass down fun.

gcc/testsuite/ChangeLog:

	* gcc.dg/loop-unswitch-7.c: New test.
	* gcc.dg/loop-unswitch-8.c: New test.
	* gcc.dg/loop-unswitch-9.c: New test.
	* gcc.dg/loop-unswitch-10.c: New test.
	* gcc.dg/loop-unswitch-11.c: New test.
	* gcc.dg/loop-unswitch-12.c: New test.
	* gcc.dg/loop-unswitch-13.c: New test.
	* gcc.dg/loop-unswitch-14.c: New test.
	* gcc.dg/loop-unswitch-15.c: New test.
	* gcc.dg/loop-unswitch-16.c: New test.
	* gcc.dg/loop-unswitch-17.c: New test.
	* gcc.dg/torture/20220518-1.c: New test.
	* gcc.dg/torture/20220518-2.c: New test.
	* gcc.dg/torture/20220525-1.c: New test.
	* gcc.dg/alias-10.c: Adjust.
	* gcc.dg/tree-ssa/loop-6.c: Likewise.
	* gcc.dg/loop-unswitch-1.c: Likewise.

Co-authored-by: Richard Biener  <rguenther@suse.de>
---
 gcc/dbgcnt.def                            |    1 +
 gcc/doc/invoke.texi                       |    3 -
 gcc/params.opt                            |    4 -
 gcc/testsuite/gcc.dg/alias-10.c           |    2 +-
 gcc/testsuite/gcc.dg/loop-unswitch-1.c    |    2 +-
 gcc/testsuite/gcc.dg/loop-unswitch-10.c   |   56 ++
 gcc/testsuite/gcc.dg/loop-unswitch-11.c   |   45 +
 gcc/testsuite/gcc.dg/loop-unswitch-12.c   |   28 +
 gcc/testsuite/gcc.dg/loop-unswitch-13.c   |   35 +
 gcc/testsuite/gcc.dg/loop-unswitch-14.c   |   60 ++
 gcc/testsuite/gcc.dg/loop-unswitch-15.c   |   15 +
 gcc/testsuite/gcc.dg/loop-unswitch-16.c   |   22 +
 gcc/testsuite/gcc.dg/loop-unswitch-17.c   |   24 +
 gcc/testsuite/gcc.dg/loop-unswitch-7.c    |   28 +
 gcc/testsuite/gcc.dg/loop-unswitch-8.c    |   31 +
 gcc/testsuite/gcc.dg/loop-unswitch-9.c    |   27 +
 gcc/testsuite/gcc.dg/torture/20220518-1.c |   39 +
 gcc/testsuite/gcc.dg/torture/20220518-2.c |   14 +
 gcc/testsuite/gcc.dg/torture/20220525-1.c |   33 +
 gcc/testsuite/gcc.dg/tree-ssa/loop-6.c    |    2 +-
 gcc/tree-cfg.cc                           |    7 +-
 gcc/tree-ssa-loop-unswitch.cc             | 1062 ++++++++++++++++-----
 22 files changed, 1288 insertions(+), 252 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-10.c
 create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-11.c
 create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-12.c
 create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-13.c
 create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-14.c
 create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-15.c
 create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-16.c
 create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-17.c
 create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-7.c
 create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-8.c
 create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-9.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/20220518-1.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/20220518-2.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/20220525-1.c
  

Comments

David Malcolm May 25, 2022, 10:35 a.m. UTC | #1
On Wed, 2022-05-25 at 10:36 +0200, Richard Biener via Gcc-patches
wrote:
> This patch adds support to unswitch loops with switch statements
> based on invariant index.  It furthermore reworks the cost model
> to allow an overall budget of statements to be created per original
> loop by all unswitching opportunities in the loop.  Compared to
> the original all unswitching opportunities in a loop are
> pre-evaluated before the first transform which will allow future
> changes to select the most profitable candidates first.
> 
> To efficiently support switch statements the pass now uses
> ranger to simplify switch statements and conditions in loop
> copies based on ranges extracted from the recorded set of
> predicates unswitched.
> 
> Bootstrapped and tested on x86_64-unknown-linux-gnu, pushed to trunk.
> 
> Richard.
> 

[...snip...]

> diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-10.c
> b/gcc/testsuite/gcc.dg/loop-unswitch-10.c
> new file mode 100644
> index 00000000000..5e4f16e2935
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/loop-unswitch-10.c
> @@ -0,0 +1,56 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-
> optimized" } */
> +
> +int
> +__attribute__((noipa))
> +foo(double *a, double *b, double *c, double *d, double *r, int size,
> int order)
> +{
> +  for (int i = 0; i < size; i++)
> +  {
> +    double tmp, tmp2;
> +
> +    switch(order)
> +    {
> +      case 0:
> +        tmp = -8 * a[i];
> +        tmp2 = 2 * b[i];
> +        break;
> +      case 1: 
> +        tmp = 3 * a[i] -  2 * b[i];
> +        tmp2 = 5 * b[i] - 2 * c[i];
> +        break;
> +      case 2:
> +        tmp = 9 * a[i] +  2 * b[i] + c[i];
> +        tmp2 = 4 * b[i] + 2 * c[i] + 8 * d[i];
> +        break;
> +      case 3:
> +        tmp = 3 * a[i] +  2 * b[i] - c[i];
> +        tmp2 = b[i] - 2 * c[i] + 8 * d[i];
> +        break;
> +      defaut:
> +        __builtin_unreachable ();

I'm guessing "defaut:" here is a typo for "default:" i.e. it's an
unused label named "defaut", when presumably you meant to have an
unreachable default case.  Does this affect the loop unswitching logic?

I wonder if we warn for this (or if we can/should?)

Looking at the patch, it seems to also be present in:
  gcc/testsuite/gcc.dg/loop-unswitch-11.c
  gcc/testsuite/gcc.dg/loop-unswitch-14.c
but I might have missed some.


Dave
  
Richard Biener May 25, 2022, 10:55 a.m. UTC | #2
On Wed, 25 May 2022, David Malcolm wrote:

> On Wed, 2022-05-25 at 10:36 +0200, Richard Biener via Gcc-patches
> wrote:
> > This patch adds support to unswitch loops with switch statements
> > based on invariant index.  It furthermore reworks the cost model
> > to allow an overall budget of statements to be created per original
> > loop by all unswitching opportunities in the loop.  Compared to
> > the original all unswitching opportunities in a loop are
> > pre-evaluated before the first transform which will allow future
> > changes to select the most profitable candidates first.
> > 
> > To efficiently support switch statements the pass now uses
> > ranger to simplify switch statements and conditions in loop
> > copies based on ranges extracted from the recorded set of
> > predicates unswitched.
> > 
> > Bootstrapped and tested on x86_64-unknown-linux-gnu, pushed to trunk.
> > 
> > Richard.
> > 
> 
> [...snip...]
> 
> > diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-10.c
> > b/gcc/testsuite/gcc.dg/loop-unswitch-10.c
> > new file mode 100644
> > index 00000000000..5e4f16e2935
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.dg/loop-unswitch-10.c
> > @@ -0,0 +1,56 @@
> > +/* { dg-do compile } */
> > +/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-
> > optimized" } */
> > +
> > +int
> > +__attribute__((noipa))
> > +foo(double *a, double *b, double *c, double *d, double *r, int size,
> > int order)
> > +{
> > +  for (int i = 0; i < size; i++)
> > +  {
> > +    double tmp, tmp2;
> > +
> > +    switch(order)
> > +    {
> > +      case 0:
> > +        tmp = -8 * a[i];
> > +        tmp2 = 2 * b[i];
> > +        break;
> > +      case 1: 
> > +        tmp = 3 * a[i] -  2 * b[i];
> > +        tmp2 = 5 * b[i] - 2 * c[i];
> > +        break;
> > +      case 2:
> > +        tmp = 9 * a[i] +  2 * b[i] + c[i];
> > +        tmp2 = 4 * b[i] + 2 * c[i] + 8 * d[i];
> > +        break;
> > +      case 3:
> > +        tmp = 3 * a[i] +  2 * b[i] - c[i];
> > +        tmp2 = b[i] - 2 * c[i] + 8 * d[i];
> > +        break;
> > +      defaut:
> > +        __builtin_unreachable ();
> 
> I'm guessing "defaut:" here is a typo for "default:" i.e. it's an
> unused label named "defaut", when presumably you meant to have an
> unreachable default case.  Does this affect the loop unswitching logic?
> 
> I wonder if we warn for this (or if we can/should?)

I guess we might want to (warn about labels in the "toplevel"
scope in switch statements).  So warn about

switch (x)
{
case 1:
bar:
};

the 'case' might be missing here (if 'bar' is a valid enumeration
constant for example) but not about

switch (x)
{
case 1:
 {
bar:
 }
};

Maybe we just want to warn when the label is otherwise unused?

> Looking at the patch, it seems to also be present in:
>   gcc/testsuite/gcc.dg/loop-unswitch-11.c
>   gcc/testsuite/gcc.dg/loop-unswitch-14.c
> but I might have missed some.

Heh - how did you spot these but me not looking over the patch
multiple times ...

And no, it doesn't change behavior so I just pushed a fix.

Richard.

> 
> 
> Dave
> 
>
  
Michael Matz May 25, 2022, 12:57 p.m. UTC | #3
Hello,

On Wed, 25 May 2022, Richard Biener via Gcc-patches wrote:

> I guess we might want to (warn about labels in the "toplevel"
> scope in switch statements).  So warn about
> 
> switch (x)
> {
> case 1:
> bar:
> };

That style is actually used quite some time in GCC itself.  Sometimes with 
statements between 'case 1:' and 'bar:'.

> Maybe we just want to warn when the label is otherwise unused?

We do with -Wunused-labels (part of -Wall).  The testcases simply aren't 
compiled with that.


Ciao,
Michael.
  

Patch

diff --git a/gcc/dbgcnt.def b/gcc/dbgcnt.def
index 3aa18cd0cf6..365d5cbc6b8 100644
--- a/gcc/dbgcnt.def
+++ b/gcc/dbgcnt.def
@@ -187,6 +187,7 @@  DEBUG_COUNTER (ira_move)
 DEBUG_COUNTER (ivopts_loop)
 DEBUG_COUNTER (lim)
 DEBUG_COUNTER (local_alloc_for_sched)
+DEBUG_COUNTER (loop_unswitch)
 DEBUG_COUNTER (match)
 DEBUG_COUNTER (merged_ipa_icf)
 DEBUG_COUNTER (phiopt_edge_range)
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index a2f85f0a4ea..12f834ff01d 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -14204,9 +14204,6 @@  The maximum depth of a loop nest suitable for complete peeling.
 @item max-unswitch-insns
 The maximum number of insns of an unswitched loop.
 
-@item max-unswitch-level
-The maximum number of branches unswitched in a single loop.
-
 @item lim-expensive
 The minimum cost of an expensive expression in the loop invariant motion.
 
diff --git a/gcc/params.opt b/gcc/params.opt
index b88e1372005..bcf1423671a 100644
--- a/gcc/params.opt
+++ b/gcc/params.opt
@@ -745,10 +745,6 @@  The maximum number of instructions to consider to unroll in a loop.
 Common Joined UInteger Var(param_max_unswitch_insns) Init(50) Param Optimization
 The maximum number of insns of an unswitched loop.
 
--param=max-unswitch-level=
-Common Joined UInteger Var(param_max_unswitch_level) Init(3) Param Optimization
-The maximum number of unswitchings in a single loop.
-
 -param=max-variable-expansions-in-unroller=
 Common Joined UInteger Var(param_max_variable_expansions) Init(1) Param Optimization
 If -fvariable-expansion-in-unroller is used, the maximum number of times that an individual variable will be expanded during loop unrolling.
diff --git a/gcc/testsuite/gcc.dg/alias-10.c b/gcc/testsuite/gcc.dg/alias-10.c
index 95d8b196cae..8472d30a6ae 100644
--- a/gcc/testsuite/gcc.dg/alias-10.c
+++ b/gcc/testsuite/gcc.dg/alias-10.c
@@ -28,4 +28,4 @@  void foo (bitmap head, bitmap_element *elt)
 }
 
 
-/* { dg-final { scan-tree-dump-times "Unswitching" 1 "unswitch"} } */
+/* { dg-final { scan-tree-dump-times "unswitching" 1 "unswitch"} } */
diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-1.c b/gcc/testsuite/gcc.dg/loop-unswitch-1.c
index f9d628df510..196cb64735e 100644
--- a/gcc/testsuite/gcc.dg/loop-unswitch-1.c
+++ b/gcc/testsuite/gcc.dg/loop-unswitch-1.c
@@ -33,4 +33,4 @@  parse_tag: ;
 }
 
 /* Test that we actually unswitched something.  */
-/* { dg-final { scan-tree-dump "Unswitching loop" "unswitch" } } */
+/* { dg-final { scan-tree-dump "unswitching loop" "unswitch" } } */
diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-10.c b/gcc/testsuite/gcc.dg/loop-unswitch-10.c
new file mode 100644
index 00000000000..5e4f16e2935
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/loop-unswitch-10.c
@@ -0,0 +1,56 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized" } */
+
+int
+__attribute__((noipa))
+foo(double *a, double *b, double *c, double *d, double *r, int size, int order)
+{
+  for (int i = 0; i < size; i++)
+  {
+    double tmp, tmp2;
+
+    switch(order)
+    {
+      case 0:
+        tmp = -8 * a[i];
+        tmp2 = 2 * b[i];
+        break;
+      case 1: 
+        tmp = 3 * a[i] -  2 * b[i];
+        tmp2 = 5 * b[i] - 2 * c[i];
+        break;
+      case 2:
+        tmp = 9 * a[i] +  2 * b[i] + c[i];
+        tmp2 = 4 * b[i] + 2 * c[i] + 8 * d[i];
+        break;
+      case 3:
+        tmp = 3 * a[i] +  2 * b[i] - c[i];
+        tmp2 = b[i] - 2 * c[i] + 8 * d[i];
+        break;
+      defaut:
+        __builtin_unreachable ();
+    }
+
+    double x = 3 * tmp + d[i] + tmp;
+    double y = 3.4f * tmp + d[i] + tmp2;
+    r[i] = x + y;
+  }
+
+  return 0;
+}
+
+#define N 16 * 1024
+double aa[N], bb[N], cc[N], dd[N], rr[N];
+
+int main()
+{
+  for (int i = 0; i < 100 * 1000; i++)
+    foo (aa, bb, cc, dd, rr, N, i % 4);
+}
+
+
+/* Test that we actually unswitched something.  */
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 0" 1 "unswitch" } } */
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 1" 1 "unswitch" } } */
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 2" 1 "unswitch" } } */
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 3" 1 "unswitch" } } */
diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-11.c b/gcc/testsuite/gcc.dg/loop-unswitch-11.c
new file mode 100644
index 00000000000..04da28c0762
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/loop-unswitch-11.c
@@ -0,0 +1,45 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized" } */
+
+int
+foo(double *a, double *b, double *c, double *d, double *r, int size, int order)
+{
+  for (int i = 0; i < size; i++)
+  {
+    double tmp, tmp2;
+
+    switch(order)
+    {
+      case 5 ... 6:
+      case 9:
+        tmp = -8 * a[i];
+        tmp2 = 2 * b[i];
+        break;
+      case 11: 
+        tmp = 3 * a[i] -  2 * b[i];
+        tmp2 = 5 * b[i] - 2 * c[i];
+        break;
+      case 22:
+        tmp = 9 * a[i] +  2 * b[i] + c[i];
+        tmp2 = 4 * b[i] + 2 * c[i] + 8 * d[i];
+        break;
+      case 33:
+        tmp = 3 * a[i] +  2 * b[i] - c[i];
+        tmp2 = b[i] - 2 * c[i] + 8 * d[i];
+        break;
+      defaut:
+        __builtin_unreachable ();
+    }
+
+    double x = 3 * tmp + d[i] + tmp;
+    double y = 3.4f * tmp + d[i] + tmp2;
+    r[i] = x + y;
+  }
+
+  return 0;
+}
+
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* \\+ 4294967291.*order.* == 9" 1 "unswitch" } } */
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 1" 1 "unswitch" } } */
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 2" 1 "unswitch" } } */
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 3" 1 "unswitch" } } */
diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-12.c b/gcc/testsuite/gcc.dg/loop-unswitch-12.c
new file mode 100644
index 00000000000..052c456846c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/loop-unswitch-12.c
@@ -0,0 +1,28 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized" } */
+
+int
+foo(double *a, double *b, double *c, double *d, double *r, int size, int order)
+{
+  for (int i = 0; i < size; i++)
+  {
+    double tmp;
+
+    if (order == 1)
+      tmp = -8 * a[i];
+    else
+      tmp = -4 * b[i];
+
+    double x = 3 * tmp + d[i] + tmp;
+
+    if (order == 1)
+      x += 2;
+
+    double y = 3.4f * tmp + d[i];
+    r[i] = x + y;
+  }
+
+  return 0;
+}
+
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .if. with condition: order.* == 1" 1 "unswitch" } } */
diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-13.c b/gcc/testsuite/gcc.dg/loop-unswitch-13.c
new file mode 100644
index 00000000000..d09c4aabc4e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/loop-unswitch-13.c
@@ -0,0 +1,35 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -funswitch-loops -fno-thread-jumps -fdump-tree-unswitch-optimized" } */
+
+int
+foo(double *a, double *b, double *c, double *d, double *r, int size, unsigned order)
+{
+  for (int i = 0; i < size; i++)
+  {
+    double tmp;
+
+    switch (order)
+      {
+      case 0 ... 4:
+	tmp = -8 * a[i];
+	break;
+      default:
+	tmp = -4 * b[i];
+	break;
+      }
+
+    double x = 3 * tmp + d[i] + tmp;
+
+    /* This and the case 0 ... 4 condition should only be unswitched once
+       since they are mutually excluded.  */
+    if (order >= 5)
+      x += 2;
+
+    double y = 3.4f * tmp + d[i];
+    r[i] = x + y;
+  }
+
+  return 0;
+}
+
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .\[^\n\r\]*. with condition" 1 "unswitch" } } */
diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-14.c b/gcc/testsuite/gcc.dg/loop-unswitch-14.c
new file mode 100644
index 00000000000..129805eb918
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/loop-unswitch-14.c
@@ -0,0 +1,60 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized --param=max-unswitch-insns=1000" } */
+
+int
+__attribute__((noipa))
+foo(double *a, double *b, double *c, double *d, double *r, int size, int order)
+{
+  for (int i = 0; i < size; i++)
+  {
+    double tmp, tmp2;
+
+    if (order <= 0)
+      tmp = 123;
+
+    switch(order)
+    {
+      case 0:
+        tmp += -8 * a[i];
+        tmp2 = 2 * b[i];
+        break;
+      case 1: 
+        tmp = 3 * a[i] -  2 * b[i];
+        tmp2 = 5 * b[i] - 2 * c[i];
+        break;
+      case 2:
+        tmp = 9 * a[i] +  2 * b[i] + c[i];
+        tmp2 = 4 * b[i] + 2 * c[i] + 8 * d[i];
+        break;
+      case 3:
+        tmp = 3 * a[i] +  2 * b[i] - c[i];
+        tmp2 = b[i] - 2 * c[i] + 8 * d[i];
+        break;
+      defaut:
+        __builtin_unreachable ();
+    }
+
+    double x = 3 * tmp + d[i] + tmp;
+    double y = 3.4f * tmp + d[i] + tmp2;
+    r[i] = x + y;
+  }
+
+  return 0;
+}
+
+#define N 16 * 1024
+double aa[N], bb[N], cc[N], dd[N], rr[N];
+
+int main()
+{
+  for (int i = 0; i < 100 * 1000; i++)
+    foo (aa, bb, cc, dd, rr, N, i % 4);
+}
+
+
+/* Test that we actually unswitched something.  */
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* <= 0" 1 "unswitch" } } */
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 0" 1 "unswitch" } } */
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 1" 1 "unswitch" } } */
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 2" 1 "unswitch" } } */
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 3" 1 "unswitch" } } */
diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-15.c b/gcc/testsuite/gcc.dg/loop-unswitch-15.c
new file mode 100644
index 00000000000..87139bb3334
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/loop-unswitch-15.c
@@ -0,0 +1,15 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized" } */
+
+void bar();
+void baz();
+void foo (int a, int b, int n)
+{
+  for (int i = 0; i < n; ++i)
+    if (a < b)
+      bar ();
+    else
+      baz ();
+}
+
+/* { dg-final { scan-tree-dump "unswitching loop . on .if. with condition:" "unswitch" } } */
diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-16.c b/gcc/testsuite/gcc.dg/loop-unswitch-16.c
new file mode 100644
index 00000000000..4b0b40015ac
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/loop-unswitch-16.c
@@ -0,0 +1,22 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized --param max-unswitch-insns=100" } */
+
+void bar (int);
+void foo (int a, int b, int c, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      if (a > 5)
+        bar (1);
+      if (b < 10)
+        bar (2);
+      if (c != 5)
+        bar (3);
+    }
+}
+
+/* Verify we can unswitch all permutations of the predicates.  */
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .if. with condition" 7 "unswitch" } } */
+/* { dg-final { scan-tree-dump "unswitching loop . on .if. with condition: a" "unswitch" } } */
+/* { dg-final { scan-tree-dump "unswitching loop . on .if. with condition: b" "unswitch" } } */
+/* { dg-final { scan-tree-dump "unswitching loop . on .if. with condition: c" "unswitch" } } */
diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-17.c b/gcc/testsuite/gcc.dg/loop-unswitch-17.c
new file mode 100644
index 00000000000..8655e09a51c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/loop-unswitch-17.c
@@ -0,0 +1,24 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized" } */
+
+int foo (int a)
+{
+  do
+    {
+      if (a == 1)
+        return 0;
+      switch (a)
+        {
+        case 1:
+          return 5;
+        case 2:
+          return 7;
+        case 3:
+          return 11;
+        default:;
+        }
+    }
+  while (1);
+}
+
+/* { dg-final { scan-tree-dump-times "unswitching loop" 3 "unswitch" } } */
diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-7.c b/gcc/testsuite/gcc.dg/loop-unswitch-7.c
new file mode 100644
index 00000000000..db2f93096cb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/loop-unswitch-7.c
@@ -0,0 +1,28 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -funswitch-loops -fno-thread-jumps -fdump-tree-unswitch-optimized" } */
+
+int
+foo(double *a, double *b, double *c, double *d, double *r, int size, float order)
+{
+  for (int i = 0; i < size; i++)
+  {
+    double tmp;
+
+    if (order == 1.f)
+      tmp = -8 * a[i];
+    else
+      tmp = -4 * b[i];
+
+    double x = 3 * tmp + d[i] + tmp;
+
+    if (order == 1.f)
+      x += 2;
+
+    double y = 3.4f * tmp + d[i];
+    r[i] = x + y;
+  }
+
+  return 0;
+}
+
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .if. with condition: order.* == 1.0e" 1 "unswitch" } } */
diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-8.c b/gcc/testsuite/gcc.dg/loop-unswitch-8.c
new file mode 100644
index 00000000000..32796e9b7f8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/loop-unswitch-8.c
@@ -0,0 +1,31 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized" } */
+
+int
+foo(double *a, double *b, double *c, double *d, double *r, int size, int order)
+{
+  for (int i = 0; i < size; i++)
+  {
+    double tmp;
+
+    if (order < 3)
+      tmp = -8 * a[i];
+    else
+      tmp = -4 * b[i];
+
+    double x = 3 * tmp + d[i] + tmp;
+
+    if (5 > order)
+      x += 2;
+
+    if (order == 12345)
+      x *= 5;
+
+    double y = 3.4f * tmp + d[i];
+    r[i] = x + y;
+  }
+
+  return 0;
+}
+
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .if. with condition: order" 3 "unswitch" } } */
diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-9.c b/gcc/testsuite/gcc.dg/loop-unswitch-9.c
new file mode 100644
index 00000000000..5e50b078bba
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/loop-unswitch-9.c
@@ -0,0 +1,27 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized" } */
+
+int
+foo(double *a, double *b, double *c, double *d, double *r, int size, int order)
+{
+  for (int i = 0; i < size; i++)
+  {
+    double tmp;
+
+    if (order == 1)
+      tmp = -8 * a[i];
+    else
+      {
+	if (order == 2)
+	  tmp = -4 * b[i];
+	else
+	  tmp = a[i];
+      }
+
+    r[i] = 3.4f * tmp + d[i];
+  }
+
+  return 0;
+}
+
+/* { dg-final { scan-tree-dump-times "unswitching loop . on .if. with condition: order" 2 "unswitch" } } */
diff --git a/gcc/testsuite/gcc.dg/torture/20220518-1.c b/gcc/testsuite/gcc.dg/torture/20220518-1.c
new file mode 100644
index 00000000000..1822aee6151
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/20220518-1.c
@@ -0,0 +1,39 @@ 
+/* { dg-do compile } */
+/* { dg-additional-options "-funswitch-loops" } */
+
+enum {
+  MOD_WVG_MASK_TEX_USE_INT,
+  MOD_WVG_MASK_TEX_USE_RED,
+  MOD_WVG_MASK_TEX_USE_BLUE,
+  MOD_WVG_MASK_TEX_USE_SAT,
+  MOD_WVG_MASK_TEX_USE_VAL,
+  MOD_WVG_MASK_TEX_USE_ALPHA
+} foo_num;
+float *foo_org_w;
+int *foo_new_w;
+float foo_fact;
+int foo_tex_use_channel, foo_i, foo_texres_0;
+void foo()
+{
+  for (; foo_num;)
+    switch (foo_tex_use_channel) {
+    case MOD_WVG_MASK_TEX_USE_INT:
+      foo_org_w[foo_i] = foo_new_w[foo_i] * foo_texres_0;
+      break;
+    case MOD_WVG_MASK_TEX_USE_RED:
+      foo_org_w[foo_i] = 0;
+    case MOD_WVG_MASK_TEX_USE_BLUE:
+      foo_org_w[foo_i] = foo_fact + foo_org_w[foo_i];
+      break;
+    case MOD_WVG_MASK_TEX_USE_SAT:
+      foo_org_w[foo_i] = foo_fact;
+      break;
+    case MOD_WVG_MASK_TEX_USE_VAL:
+      foo_org_w[foo_i] = 0;
+    case MOD_WVG_MASK_TEX_USE_ALPHA:
+      foo_org_w[foo_i] = foo_fact + foo_org_w[foo_i];
+      break;
+    default:
+      foo_org_w[foo_i] = foo_new_w[foo_i] * foo_texres_0;
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/torture/20220518-2.c b/gcc/testsuite/gcc.dg/torture/20220518-2.c
new file mode 100644
index 00000000000..af70d7f6634
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/20220518-2.c
@@ -0,0 +1,14 @@ 
+/* { dg-do compile } */
+/* { dg-additional-options "-funswitch-loops" } */
+
+int Get_Spline_Val_sp_0, Get_Spline_Val_k;
+double Get_Spline_Val_p, Get_Spline_Val_se_0_0_0;
+double *Get_Spline_Val_v;
+void Get_Spline_Val() {
+  int i;
+  for (;;)
+    if (i > Get_Spline_Val_sp_0)
+      Get_Spline_Val_k = Get_Spline_Val_se_0_0_0;
+    else if (Get_Spline_Val_sp_0 == 1)
+      Get_Spline_Val_v[Get_Spline_Val_k] = Get_Spline_Val_p;
+}
diff --git a/gcc/testsuite/gcc.dg/torture/20220525-1.c b/gcc/testsuite/gcc.dg/torture/20220525-1.c
new file mode 100644
index 00000000000..55dad3140f7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/20220525-1.c
@@ -0,0 +1,33 @@ 
+/* { dg-do compile } */
+/* { dg-additional-options "-funswitch-loops" } */
+
+int LIST_1, mb_pred_b_d4x4spatial_dec_picture_l0_rFrame,
+    mb_pred_b_d4x4spatial_dec_picture_l1_rFrame;
+typedef struct {
+    char ref_idx[2];
+} PicMotionParams;
+PicMotionParams mb_pred_b_d4x4spatial_dec_picture_mv_info;
+int get_colocated_info_4x4___trans_tmp_1, get_colocated_info_4x4_list1_0;
+int get_colocated_info_4x4()
+{
+  int moving =
+      get_colocated_info_4x4_list1_0 && get_colocated_info_4x4___trans_tmp_1;
+  return moving;
+}
+void mb_pred_b_d4x4spatial_dec_picture()
+{
+  char k;
+  for (;;)
+    {
+      k = 0;
+      for (; k < 4; k++)
+        if (mb_pred_b_d4x4spatial_dec_picture_l0_rFrame
+            || mb_pred_b_d4x4spatial_dec_picture_l1_rFrame == 0)
+          {
+            int is_not_moving = get_colocated_info_4x4();
+            if (mb_pred_b_d4x4spatial_dec_picture_l1_rFrame)
+              if (is_not_moving)
+                mb_pred_b_d4x4spatial_dec_picture_mv_info.ref_idx[LIST_1] = 1;
+          }
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/loop-6.c b/gcc/testsuite/gcc.dg/tree-ssa/loop-6.c
index 60447601238..f9eb5c65ebe 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/loop-6.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/loop-6.c
@@ -19,7 +19,7 @@  void xxx(void)
 
 /* Loop should be unswitched.  */
 
-/* { dg-final { scan-tree-dump-times "Unswitching loop" 1 "unswitch" } } */
+/* { dg-final { scan-tree-dump-times "unswitching loop" 1 "unswitch" } } */
 
 /* In effect there should be exactly three conditional jumps in the final program.  */
 
diff --git a/gcc/tree-cfg.cc b/gcc/tree-cfg.cc
index 0a962dd5421..8de1b144a42 100644
--- a/gcc/tree-cfg.cc
+++ b/gcc/tree-cfg.cc
@@ -9028,11 +9028,16 @@  gimple_lv_add_condition_to_bb (basic_block first_head ATTRIBUTE_UNUSED,
   edge e0;
 
   /* Build new conditional expr */
+  gsi = gsi_last_bb (cond_bb);
+
+  cond_expr = force_gimple_operand_gsi_1 (&gsi, cond_expr,
+					  is_gimple_condexpr_for_cond,
+					  NULL_TREE, false,
+					  GSI_CONTINUE_LINKING);
   new_cond_expr = gimple_build_cond_from_tree (cond_expr,
 					       NULL_TREE, NULL_TREE);
 
   /* Add new cond in cond_bb.  */
-  gsi = gsi_last_bb (cond_bb);
   gsi_insert_after (&gsi, new_cond_expr, GSI_NEW_STMT);
 
   /* Adjust edges appropriately to connect new head with first head
diff --git a/gcc/tree-ssa-loop-unswitch.cc b/gcc/tree-ssa-loop-unswitch.cc
index 2927f308234..f32f1a78f00 100644
--- a/gcc/tree-ssa-loop-unswitch.cc
+++ b/gcc/tree-ssa-loop-unswitch.cc
@@ -38,6 +38,10 @@  along with GCC; see the file COPYING3.  If not see
 #include "cfghooks.h"
 #include "tree-ssa-loop-manip.h"
 #include "tree-vectorizer.h"
+#include "tree-pretty-print.h"
+#include "gimple-range.h"
+#include "dbgcnt.h"
+#include "cfganal.h"
 
 /* This file implements the loop unswitching, i.e. transformation of loops like
 
@@ -75,9 +79,137 @@  along with GCC; see the file COPYING3.  If not see
    tree-ssa-loop-im.cc ensures that all the suitable conditions are in this
    shape.  */
 
-static class loop *tree_unswitch_loop (class loop *, basic_block, tree);
-static bool tree_unswitch_single_loop (class loop *, int);
-static tree tree_may_unswitch_on (basic_block, class loop *);
+/* Loop unswitching algorithm for innermost loops works in the following steps:
+
+   1) Number of instructions is estimated for each BB that belongs to a loop.
+   2) Unswitching candidates are found for gcond and gswitch statements
+      (note that an unswitching predicate for a gswitch actually corresponds
+       to a non-default edge so it can contain multiple cases).
+   3) The so called unswitch predicates are stored in a cache where the
+      gimple_uid of the last stmt in a basic-block is an index to the cache.
+   4) We consider one by one the unswitching candidates and calculate BBs that
+      will be reachable in the unswitch version.
+   5) A selected predicate is chosen and we simplify the CFG (dead edges) in
+      both versions of the loop.  We utilize both Ranger for condition
+      simplification and also symbol equivalence.  The folded if conditions
+      are replaced with true/false values, while for gswitch we mark the
+      corresponding edges with a pass-defined unreachable flag.
+   6) Every time we unswitch a loop, we save unswitch_predicate to a vector
+      together with information if true or false edge was taken.  Doing that
+      we have a so called PREDICATE_PATH that is utilized for simplification
+      of the cloned loop.
+   7) The process is repeated until we reach a growth threshold or all
+      unswitching opportunities are taken.  */
+
+/* A tuple that holds a GENERIC condition and value range for an unswitching
+   predicate.  */
+
+struct unswitch_predicate
+{
+  /* CTOR for a switch edge predicate.  */
+  unswitch_predicate (tree cond, tree lhs_, int edge_index_, edge e,
+		      const int_range_max& edge_range)
+    : condition (cond), lhs (lhs_),
+      true_range (edge_range), edge_index (edge_index_), switch_p (true)
+  {
+    gcc_assert (!(e->flags & (EDGE_TRUE_VALUE|EDGE_FALSE_VALUE))
+		&& irange::supports_type_p (TREE_TYPE (lhs)));
+    false_range = true_range;
+    if (!false_range.varying_p ()
+	&& !false_range.undefined_p ())
+      false_range.invert ();
+    num = predicates->length ();
+    predicates->safe_push (this);
+  }
+
+  /* CTOR for a GIMPLE condition statement.  */
+  unswitch_predicate (gcond *stmt)
+    : switch_p (false)
+  {
+    if (EDGE_SUCC (gimple_bb (stmt), 0)->flags & EDGE_TRUE_VALUE)
+      edge_index = 0;
+    else
+      edge_index = 1;
+    lhs = gimple_cond_lhs (stmt);
+    tree rhs = gimple_cond_rhs (stmt);
+    enum tree_code code = gimple_cond_code (stmt);
+    condition = build2 (code, boolean_type_node, lhs, rhs);
+    if (irange::supports_type_p (TREE_TYPE (lhs)))
+      {
+	auto range_op = range_op_handler (code, TREE_TYPE (lhs));
+	int_range<2> rhs_range (TREE_TYPE (rhs));
+	if (CONSTANT_CLASS_P (rhs))
+	  rhs_range.set (rhs);
+	if (!range_op->op1_range (true_range, TREE_TYPE (lhs),
+				  int_range<2> (boolean_true_node,
+						boolean_true_node), rhs_range)
+	    || !range_op->op1_range (false_range, TREE_TYPE (lhs),
+				     int_range<2> (boolean_false_node,
+						   boolean_false_node),
+				     rhs_range))
+	  {
+	    true_range.set_varying (TREE_TYPE (lhs));
+	    false_range.set_varying (TREE_TYPE (lhs));
+	  }
+      }
+    num = predicates->length ();
+    predicates->safe_push (this);
+  }
+
+  /* Copy ranges for purpose of usage in predicate path.  */
+
+  inline void
+  copy_merged_ranges ()
+  {
+    merged_true_range = true_range;
+    merged_false_range = false_range;
+  }
+
+  /* GENERIC unswitching expression testing LHS against CONSTANT.  */
+  tree condition;
+
+  /* LHS of the expression.  */
+  tree lhs;
+
+  /* Initial ranges (when the expression is true/false) for the expression.  */
+  int_range_max true_range = {}, false_range = {};
+
+  /* Modified range that is part of a predicate path.  */
+  int_range_max merged_true_range = {}, merged_false_range = {};
+
+  /* Index of the edge the predicate belongs to in the successor vector.  */
+  int edge_index;
+
+  /* Whether the predicate was created from a switch statement.  */
+  bool switch_p;
+
+  /* The number of the predicate in the predicates vector below.  */
+  unsigned num;
+
+  /* Vector of all used predicates, used for assigning a unique id that
+     can be used for bitmap operations.  */
+  static vec<unswitch_predicate *> *predicates;
+};
+
+vec<unswitch_predicate *> *unswitch_predicate::predicates;
+
+/* Ranger instance used in the pass.  */
+static gimple_ranger *ranger = NULL;
+
+/* Cache storage for unswitch_predicate belonging to a basic block.  */
+static vec<vec<unswitch_predicate *>> *bb_predicates;
+
+/* The type represents a predicate path leading to a basic block.  */
+typedef vec<std::pair<unswitch_predicate *, bool>> predicate_vector;
+
+static class loop *tree_unswitch_loop (class loop *, edge, tree);
+static bool tree_unswitch_single_loop (class loop *, dump_user_location_t,
+				       predicate_vector &predicate_path,
+				       unsigned loop_size, unsigned &budget,
+				       int ignored_edge_flag, bitmap);
+static void
+find_unswitching_predicates_for_bb (basic_block bb, class loop *loop,
+				    vec<unswitch_predicate *> &candidates);
 static bool tree_unswitch_outer_loop (class loop *);
 static edge find_loop_guard (class loop *, vec<gimple *>&);
 static bool empty_bb_without_guard_p (class loop *, basic_block,
@@ -86,26 +218,154 @@  static bool used_outside_loop_p (class loop *, tree, vec<gimple *>&);
 static void hoist_guard (class loop *, edge);
 static bool check_exit_phi (class loop *);
 static tree get_vop_from_header (class loop *);
+static void clean_up_after_unswitching (int);
+
+/* Return vector of predicates that belong to a basic block.  */
+
+static vec<unswitch_predicate *> &
+get_predicates_for_bb (basic_block bb)
+{
+  gimple *last = last_stmt (bb);
+  return (*bb_predicates)[last == NULL ? 0 : gimple_uid (last)];
+}
+
+/* Save predicates that belong to a basic block.  */
+
+static void
+set_predicates_for_bb (basic_block bb, vec<unswitch_predicate *> predicates)
+{
+  gimple_set_uid (last_stmt (bb), bb_predicates->length ());
+  bb_predicates->safe_push (predicates);
+}
+
+/* Initialize LOOP information reused during the unswitching pass.
+   Return total number of instructions in the loop.  */
+
+static unsigned
+init_loop_unswitch_info (class loop *loop)
+{
+  unsigned total_insns = 0;
+
+  /* Calculate instruction count.  */
+  basic_block *bbs = get_loop_body (loop);
+  for (unsigned i = 0; i < loop->num_nodes; i++)
+    {
+      unsigned insns = 0;
+      for (gimple_stmt_iterator gsi = gsi_start_bb (bbs[i]); !gsi_end_p (gsi);
+	   gsi_next (&gsi))
+	insns += estimate_num_insns (gsi_stmt (gsi), &eni_size_weights);
+
+      bbs[i]->aux = (void *)(uintptr_t)insns;
+      total_insns += insns;
+    }
+
+  /* Find all unswitching candidates.  */
+  for (unsigned i = 0; i != loop->num_nodes; i++)
+    {
+      /* Find a bb to unswitch on.  */
+      vec<unswitch_predicate *> candidates;
+      candidates.create (1);
+      find_unswitching_predicates_for_bb (bbs[i], loop, candidates);
+      if (!candidates.is_empty ())
+	set_predicates_for_bb (bbs[i], candidates);
+      else
+	{
+	  candidates.release ();
+	  gimple *last = last_stmt (bbs[i]);
+	  if (last != NULL)
+	    gimple_set_uid (last, 0);
+	}
+    }
+
+  free (bbs);
+
+  return total_insns;
+}
 
 /* Main entry point.  Perform loop unswitching on all suitable loops.  */
 
 unsigned int
-tree_ssa_unswitch_loops (void)
+tree_ssa_unswitch_loops (function *fun)
 {
-  bool changed = false;
+  bool changed_unswitch = false;
+  bool changed_hoist = false;
+  auto_edge_flag ignored_edge_flag (fun);
+
+  ranger = enable_ranger (fun);
 
   /* Go through all loops starting from innermost.  */
-  for (auto loop : loops_list (cfun, LI_FROM_INNERMOST))
+  for (auto loop : loops_list (fun, LI_FROM_INNERMOST))
     {
       if (!loop->inner)
-	/* Unswitch innermost loop.  */
-	changed |= tree_unswitch_single_loop (loop, 0);
+	{
+	  /* Perform initial tests if unswitch is eligible.  */
+	  dump_user_location_t loc = find_loop_location (loop);
+
+	  /* Do not unswitch in cold regions. */
+	  if (optimize_loop_for_size_p (loop))
+	    {
+	      if (dump_enabled_p ())
+		dump_printf_loc (MSG_NOTE, loc,
+				 "Not unswitching cold loops\n");
+	      continue;
+	    }
+
+	  /* If the loop is not expected to iterate, there is no need
+	     for unswitching.  */
+	  HOST_WIDE_INT iterations = estimated_loop_iterations_int (loop);
+	  if (iterations < 0)
+	    iterations = likely_max_loop_iterations_int (loop);
+	  if (iterations >= 0 && iterations <= 1)
+	    {
+	      if (dump_enabled_p ())
+		dump_printf_loc (MSG_NOTE, loc,
+				 "Not unswitching, loop is not expected"
+				 " to iterate\n");
+	      continue;
+	    }
+
+	  bb_predicates = new vec<vec<unswitch_predicate *>> ();
+	  bb_predicates->safe_push (vec<unswitch_predicate *> ());
+	  unswitch_predicate::predicates = new vec<unswitch_predicate *> ();
+
+	  /* Unswitch innermost loop.  */
+	  unsigned int loop_size = init_loop_unswitch_info (loop);
+	  unsigned int budget = loop_size + param_max_unswitch_insns;
+
+	  predicate_vector predicate_path;
+	  predicate_path.create (8);
+	  auto_bitmap handled;
+	  changed_unswitch
+	    |= tree_unswitch_single_loop (loop, loc, predicate_path,
+					  loop_size, budget,
+					  ignored_edge_flag, handled);
+	  predicate_path.release ();
+
+	  for (auto predlist : bb_predicates)
+	    predlist.release ();
+	  bb_predicates->release ();
+	  delete bb_predicates;
+	  bb_predicates = NULL;
+
+	  for (auto pred : unswitch_predicate::predicates)
+	    delete pred;
+	  unswitch_predicate::predicates->release ();
+	  delete unswitch_predicate::predicates;
+	  unswitch_predicate::predicates = NULL;
+	}
       else
-	changed |= tree_unswitch_outer_loop (loop);
+	changed_hoist |= tree_unswitch_outer_loop (loop);
     }
 
-  if (changed)
+  disable_ranger (fun);
+  clear_aux_for_blocks ();
+
+  if (changed_unswitch)
+    clean_up_after_unswitching (ignored_edge_flag);
+
+  if (changed_unswitch || changed_hoist)
     return TODO_cleanup_cfg;
+
   return 0;
 }
 
@@ -184,319 +444,588 @@  is_maybe_undefined (const tree name, gimple *stmt, class loop *loop)
 }
 
 /* Checks whether we can unswitch LOOP on condition at end of BB -- one of its
-   basic blocks (for what it means see comments below).  */
+   basic blocks (for what it means see comments below).
+   All candidates all filled to the provided vector CANDIDATES.  */
 
-static tree
-tree_may_unswitch_on (basic_block bb, class loop *loop)
+static void
+find_unswitching_predicates_for_bb (basic_block bb, class loop *loop,
+				    vec<unswitch_predicate *> &candidates)
 {
   gimple *last, *def;
-  gcond *stmt;
-  tree cond, use;
+  tree use;
   basic_block def_bb;
   ssa_op_iter iter;
 
   /* BB must end in a simple conditional jump.  */
   last = last_stmt (bb);
-  if (!last || gimple_code (last) != GIMPLE_COND)
-    return NULL_TREE;
-  stmt = as_a <gcond *> (last);
-
-  /* To keep the things simple, we do not directly remove the conditions,
-     but just replace tests with 0 != 0 resp. 1 != 0.  Prevent the infinite
-     loop where we would unswitch again on such a condition.  */
-  if (gimple_cond_true_p (stmt) || gimple_cond_false_p (stmt))
-    return NULL_TREE;
-
-  /* Condition must be invariant.  */
-  FOR_EACH_SSA_TREE_OPERAND (use, stmt, iter, SSA_OP_USE)
+  if (!last)
+    return;
+
+  if (gcond *stmt = safe_dyn_cast <gcond *> (last))
     {
-      def = SSA_NAME_DEF_STMT (use);
+      /* To keep the things simple, we do not directly remove the conditions,
+	 but just replace tests with 0 != 0 resp. 1 != 0.  Prevent the infinite
+	 loop where we would unswitch again on such a condition.  */
+      if (gimple_cond_true_p (stmt) || gimple_cond_false_p (stmt))
+	return;
+
+      /* At least the LHS needs to be symbolic.  */
+      if (TREE_CODE (gimple_cond_lhs (stmt)) != SSA_NAME)
+	return;
+
+      /* Condition must be invariant.  */
+      FOR_EACH_SSA_TREE_OPERAND (use, stmt, iter, SSA_OP_USE)
+	{
+	  def = SSA_NAME_DEF_STMT (use);
+	  def_bb = gimple_bb (def);
+	  if (def_bb
+	      && flow_bb_inside_loop_p (loop, def_bb))
+	    return;
+	  /* Unswitching on undefined values would introduce undefined
+	     behavior that the original program might never exercise.  */
+	  if (is_maybe_undefined (use, stmt, loop))
+	    return;
+	}
+
+      unswitch_predicate *predicate = new unswitch_predicate (stmt);
+      candidates.safe_push (predicate);
+    }
+  else if (gswitch *stmt = safe_dyn_cast <gswitch *> (last))
+    {
+      unsigned nlabels = gimple_switch_num_labels (stmt);
+      tree idx = gimple_switch_index (stmt);
+      if (TREE_CODE (idx) != SSA_NAME
+	  || nlabels < 1)
+	return;
+      /* Index must be invariant.  */
+      def = SSA_NAME_DEF_STMT (idx);
       def_bb = gimple_bb (def);
       if (def_bb
 	  && flow_bb_inside_loop_p (loop, def_bb))
-	return NULL_TREE;
+	return;
       /* Unswitching on undefined values would introduce undefined
 	 behavior that the original program might never exercise.  */
-      if (is_maybe_undefined (use, stmt, loop))
-	return NULL_TREE;
-    }
+      if (is_maybe_undefined (idx, stmt, loop))
+	return;
+
+      /* Build compound expression for all outgoing edges of the switch.  */
+      auto_vec<tree, 16> preds;
+      auto_vec<int_range_max> edge_range;
+      preds.safe_grow_cleared (EDGE_COUNT (gimple_bb (stmt)->succs), true);
+      edge_range.safe_grow_cleared (EDGE_COUNT (gimple_bb (stmt)->succs), true);
+      edge e;
+      edge_iterator ei;
+      unsigned edge_index = 0;
+      FOR_EACH_EDGE (e, ei, gimple_bb (stmt)->succs)
+	e->aux = (void *)(uintptr_t)edge_index++;
+      for (unsigned i = 1; i < gimple_switch_num_labels (stmt); ++i)
+	{
+	  tree lab = gimple_switch_label (stmt, i);
+	  tree cmp;
+	  int_range<2> lab_range;
+	  if (CASE_HIGH (lab) != NULL_TREE)
+	    {
+	      tree cmp1 = fold_build2 (GE_EXPR, boolean_type_node, idx,
+				       CASE_LOW (lab));
+	      tree cmp2 = fold_build2 (LE_EXPR, boolean_type_node, idx,
+				       CASE_HIGH (lab));
+	      cmp = fold_build2 (BIT_AND_EXPR, boolean_type_node, cmp1, cmp2);
+	      lab_range.set (CASE_LOW (lab), CASE_HIGH (lab));
+	    }
+	  else
+	    {
+	      cmp = fold_build2 (EQ_EXPR, boolean_type_node, idx,
+				 CASE_LOW (lab));
+	      lab_range.set (CASE_LOW (lab));
+	    }
 
-  cond = build2 (gimple_cond_code (stmt), boolean_type_node,
-		 gimple_cond_lhs (stmt), gimple_cond_rhs (stmt));
+	  /* Combine the expression with the existing one.  */
+	  basic_block dest = label_to_block (cfun, CASE_LABEL (lab));
+	  e = find_edge (gimple_bb (stmt), dest);
+	  tree &expr = preds[(uintptr_t)e->aux];
+	  if (expr == NULL_TREE)
+	    expr = cmp;
+	  else
+	    expr = fold_build2 (BIT_IOR_EXPR, boolean_type_node, expr, cmp);
+	  edge_range[(uintptr_t)e->aux].union_ (lab_range);
+	}
 
-  return cond;
+      /* Now register the predicates.  */
+      for (edge_index = 0; edge_index < preds.length (); ++edge_index)
+	{
+	  edge e = EDGE_SUCC (gimple_bb (stmt), edge_index);
+	  e->aux = NULL;
+	  if (preds[edge_index] != NULL_TREE)
+	    {
+	      unswitch_predicate *predicate
+		= new unswitch_predicate (preds[edge_index], idx,
+					  edge_index, e,
+					  edge_range[edge_index]);
+	      candidates.safe_push (predicate);
+	    }
+	}
+    }
 }
 
-/* Simplifies COND using checks in front of the entry of the LOOP.  Just very
-   simplish (sufficient to prevent us from duplicating loop in unswitching
-   unnecessarily).  */
+/* Merge ranges for the last item of PREDICATE_PATH with a predicate
+   that shared the same LHS.  */
 
-static tree
-simplify_using_entry_checks (class loop *loop, tree cond)
+static void
+merge_last (predicate_vector &predicate_path)
 {
-  edge e = loop_preheader_edge (loop);
-  gimple *stmt;
+  unswitch_predicate *last_predicate = predicate_path.last ().first;
 
-  while (1)
+  for (int i = predicate_path.length () - 2; i >= 0; i--)
     {
-      stmt = last_stmt (e->src);
-      if (stmt
-	  && gimple_code (stmt) == GIMPLE_COND
-	  && gimple_cond_code (stmt) == TREE_CODE (cond)
-	  && operand_equal_p (gimple_cond_lhs (stmt),
-			      TREE_OPERAND (cond, 0), 0)
-	  && operand_equal_p (gimple_cond_rhs (stmt),
-			      TREE_OPERAND (cond, 1), 0))
-	return (e->flags & EDGE_TRUE_VALUE
-		? boolean_true_node
-		: boolean_false_node);
-
-      if (!single_pred_p (e->src))
-	return cond;
-
-      e = single_pred_edge (e->src);
-      if (e->src == ENTRY_BLOCK_PTR_FOR_FN (cfun))
-	return cond;
+      unswitch_predicate *predicate = predicate_path[i].first;
+      bool true_edge = predicate_path[i].second;
+
+      if (operand_equal_p (predicate->lhs, last_predicate->lhs, 0))
+	{
+	  irange &other = (true_edge ? predicate->merged_true_range
+			   : predicate->merged_false_range);
+	  last_predicate->merged_true_range.intersect (other);
+	  last_predicate->merged_false_range.intersect (other);
+	  return;
+	}
     }
 }
 
-/* Unswitch single LOOP.  NUM is number of unswitchings done; we do not allow
-   it to grow too much, it is too easy to create example on that the code would
-   grow exponentially.  */
+/* Add PREDICATE to PREDICATE_PATH on TRUE_EDGE.  */
 
-static bool
-tree_unswitch_single_loop (class loop *loop, int num)
+static void
+add_predicate_to_path (predicate_vector &predicate_path,
+		       unswitch_predicate *predicate, bool true_edge)
 {
-  basic_block *bbs;
-  class loop *nloop;
-  unsigned i, found;
-  tree cond = NULL_TREE;
-  gimple *stmt;
-  bool changed = false;
-  HOST_WIDE_INT iterations;
-
-  dump_user_location_t loc = find_loop_location (loop);
+  predicate->copy_merged_ranges ();
+  predicate_path.safe_push (std::make_pair (predicate, true_edge));
+  merge_last (predicate_path);
+}
 
-  /* Perform initial tests if unswitch is eligible.  */
-  if (num == 0)
+static bool
+find_range_for_lhs (predicate_vector &predicate_path, tree lhs,
+		    int_range_max &range)
+{
+  for (int i = predicate_path.length () - 1; i >= 0; i--)
     {
-      /* Do not unswitch in cold regions. */
-      if (optimize_loop_for_size_p (loop))
-	{
-	  if (dump_enabled_p ())
-	    dump_printf_loc (MSG_NOTE, loc,
-			     "Not unswitching cold loops\n");
-	  return false;
-	}
+      unswitch_predicate *predicate = predicate_path[i].first;
+      bool true_edge = predicate_path[i].second;
 
-      /* The loop should not be too large, to limit code growth. */
-      if (tree_num_loop_insns (loop, &eni_size_weights)
-	  > (unsigned) param_max_unswitch_insns)
+      if (operand_equal_p (predicate->lhs, lhs, 0))
 	{
-	  if (dump_enabled_p ())
-	    dump_printf_loc (MSG_NOTE, loc,
-			     "Not unswitching, loop too big\n");
-	  return false;
+	  range = (true_edge ? predicate->merged_true_range
+		   : predicate->merged_false_range);
+	  return !range.undefined_p ();
 	}
+    }
 
-      /* If the loop is not expected to iterate, there is no need
-	 for unswitching.  */
-      iterations = estimated_loop_iterations_int (loop);
-      if (iterations < 0)
-        iterations = likely_max_loop_iterations_int (loop);
-      if (iterations >= 0 && iterations <= 1)
+  return false;
+}
+
+/* Simplifies STMT using the predicate we unswitched on which is the last
+   in PREDICATE_PATH.  For switch statements add newly unreachable edges
+   to IGNORED_EDGES (but do not set IGNORED_EDGE_FLAG on them).  */
+
+static tree
+evaluate_control_stmt_using_entry_checks (gimple *stmt,
+					  predicate_vector &predicate_path,
+					  int ignored_edge_flag,
+					  hash_set<edge> *ignored_edges)
+{
+  unswitch_predicate *last_predicate = predicate_path.last ().first;
+  bool true_edge = predicate_path.last ().second;
+
+  if (gcond *cond = dyn_cast<gcond *> (stmt))
+    {
+      tree lhs = gimple_cond_lhs (cond);
+      if (!operand_equal_p (lhs, last_predicate->lhs))
+	return NULL_TREE;
+      /* Try a symbolic match which works for floating point and fully
+	 symbolic conditions.  */
+      if (gimple_cond_code (cond) == TREE_CODE (last_predicate->condition)
+	  && operand_equal_p (gimple_cond_rhs (cond),
+			      TREE_OPERAND (last_predicate->condition, 1)))
+	return true_edge ? boolean_true_node : boolean_false_node;
+      /* Else try ranger if it supports LHS.  */
+      else if (irange::supports_type_p (TREE_TYPE (lhs)))
 	{
-	  if (dump_enabled_p ())
-	    dump_printf_loc (MSG_NOTE, loc,
-			     "Not unswitching, loop is not expected"
-			     " to iterate\n");
-	  return false;
+	  int_range<2> r;
+	  int_range_max path_range;
+
+	  if (find_range_for_lhs (predicate_path, lhs, path_range)
+	      && fold_range (r, cond, path_range)
+	      && r.singleton_p ())
+	    return r.zero_p () ? boolean_false_node : boolean_true_node;
 	}
     }
+  else if (gswitch *swtch = dyn_cast<gswitch *> (stmt))
+    {
+      unsigned nlabels = gimple_switch_num_labels (swtch);
 
-  i = 0;
-  bbs = get_loop_body (loop);
-  found = loop->num_nodes;
+      tree idx = gimple_switch_index (swtch);
 
-  while (1)
-    {
-      /* Find a bb to unswitch on.  */
-      for (; i < loop->num_nodes; i++)
-	if ((cond = tree_may_unswitch_on (bbs[i], loop)))
-	  break;
+      /* Already folded switch.  */
+      if (TREE_CONSTANT (idx))
+	return NULL_TREE;
 
-      if (i == loop->num_nodes)
+      int_range_max path_range;
+      if (!find_range_for_lhs (predicate_path, idx, path_range))
+	return NULL_TREE;
+
+      tree result = NULL_TREE;
+      edge single_edge = NULL;
+      for (unsigned i = 0; i < nlabels; ++i)
 	{
-	  if (dump_enabled_p ()
-	      && num > param_max_unswitch_level)
-	    dump_printf_loc (MSG_MISSED_OPTIMIZATION, loc,
-			     "Not unswitching anymore, hit max level\n");
+	  tree lab = gimple_switch_label (swtch, i);
+	  basic_block dest = label_to_block (cfun, CASE_LABEL (lab));
+	  edge e = find_edge (gimple_bb (stmt), dest);
+	  if (e->flags & ignored_edge_flag)
+	    continue;
 
-	  if (found == loop->num_nodes)
+	  int_range_max r;
+	  if (!ranger->gori ().outgoing_edge_range_p (r, e, idx,
+						      *get_global_range_query ()))
+	    continue;
+	  r.intersect (path_range);
+	  if (r.undefined_p ())
+	    ignored_edges->add (e);
+	  else
 	    {
-	      free (bbs);
-	      return changed;
+	      if (!single_edge)
+		{
+		  single_edge = e;
+		  result = CASE_LOW (lab);
+		}
+	      else if (single_edge != e)
+		result = NULL;
 	    }
-	  break;
 	}
 
-      cond = simplify_using_entry_checks (loop, cond);
-      stmt = last_stmt (bbs[i]);
-      if (integer_nonzerop (cond))
-	{
-	  /* Remove false path.  */
-	  gimple_cond_set_condition_from_tree (as_a <gcond *> (stmt),
-					       boolean_true_node);
-	  changed = true;
-	}
-      else if (integer_zerop (cond))
+      /* Only one edge from the switch is alive.  */
+      if (single_edge && result)
+	return result;
+    }
+
+  return NULL_TREE;
+}
+
+/* Simplify LOOP based on PREDICATE_PATH where dead edges are properly
+   marked.  */
+
+static bool
+simplify_loop_version (class loop *loop, predicate_vector &predicate_path,
+		       int ignored_edge_flag, bitmap handled)
+{
+  bool changed = false;
+  basic_block *bbs = get_loop_body (loop);
+
+  hash_set<edge> ignored_edges;
+  for (unsigned i = 0; i != loop->num_nodes; i++)
+    {
+      vec<unswitch_predicate *> &predicates = get_predicates_for_bb (bbs[i]);
+      if (predicates.is_empty ())
+	continue;
+
+      gimple *stmt = last_stmt (bbs[i]);
+      tree folded = evaluate_control_stmt_using_entry_checks (stmt,
+							      predicate_path,
+							      ignored_edge_flag,
+							      &ignored_edges);
+
+      if (gcond *cond = dyn_cast<gcond *> (stmt))
 	{
-	  /* Remove true path.  */
-	  gimple_cond_set_condition_from_tree (as_a <gcond *> (stmt),
-					       boolean_false_node);
-	  changed = true;
+	  if (folded)
+	    {
+	      /* Remove path.  */
+	      if (integer_nonzerop (folded))
+		gimple_cond_set_condition_from_tree (cond, boolean_true_node);
+	      else
+		gimple_cond_set_condition_from_tree (cond, boolean_false_node);
+
+	      gcc_assert (predicates.length () == 1);
+	      bitmap_set_bit (handled, predicates[0]->num);
+
+	      update_stmt (cond);
+	      changed = true;
+	    }
 	}
-      /* Do not unswitch too much.  */
-      else if (num > param_max_unswitch_level)
+      else if (gswitch *swtch = dyn_cast<gswitch *> (stmt))
 	{
-	  i++;
-	  continue;
+	  edge e;
+	  edge_iterator ei;
+	  FOR_EACH_EDGE (e, ei, bbs[i]->succs)
+	    if (ignored_edges.contains (e))
+	      e->flags |= ignored_edge_flag;
+
+	  for (unsigned j = 0; j < predicates.length (); j++)
+	    {
+	      edge e = EDGE_SUCC (bbs[i], predicates[j]->edge_index);
+	      if (ignored_edges.contains (e))
+		bitmap_set_bit (handled, predicates[j]->num);
+	    }
+
+	  if (folded)
+	    {
+	      gimple_switch_set_index (swtch, folded);
+	      update_stmt (swtch);
+	      changed = true;
+	    }
 	}
-      /* In nested tree_unswitch_single_loop first optimize all conditions
-	 using entry checks, then discover still reachable blocks in the
-	 loop and find the condition only among those still reachable bbs.  */
-      else if (num != 0)
+    }
+
+  free (bbs);
+  return changed;
+}
+
+/* Evaluate reachable blocks in LOOP and call VISIT on them, aborting the
+   DFS walk if VISIT returns true.  When PREDICATE_PATH is specified then
+   take into account that when computing reachability, otherwise just
+   look at the simplified state and IGNORED_EDGE_FLAG.  */
+
+template <typename VisitOp>
+static void
+evaluate_bbs (class loop *loop, predicate_vector *predicate_path,
+	      int ignored_edge_flag, VisitOp visit)
+{
+  auto_bb_flag reachable_flag (cfun);
+  auto_vec<basic_block, 10> worklist (loop->num_nodes);
+  auto_vec<basic_block, 10> reachable (loop->num_nodes);
+  hash_set<edge> ignored_edges;
+
+  loop->header->flags |= reachable_flag;
+  worklist.quick_push (loop->header);
+  reachable.safe_push (loop->header);
+
+  while (!worklist.is_empty ())
+    {
+      edge e;
+      edge_iterator ei;
+      int flags = ignored_edge_flag;
+      basic_block bb = worklist.pop ();
+
+      if (visit (bb))
+	break;
+
+      gimple *last = last_stmt (bb);
+      if (gcond *cond = safe_dyn_cast <gcond *> (last))
 	{
-	  if (found == loop->num_nodes)
-	    found = i;
-	  i++;
-	  continue;
+	  if (gimple_cond_true_p (cond))
+	    flags = EDGE_FALSE_VALUE;
+	  else if (gimple_cond_false_p (cond))
+	    flags = EDGE_TRUE_VALUE;
+	  else if (predicate_path)
+	    {
+	      tree res;
+	      if (!get_predicates_for_bb (bb).is_empty ()
+		  && (res = evaluate_control_stmt_using_entry_checks
+			      (cond, *predicate_path, ignored_edge_flag,
+			       &ignored_edges)))
+		flags = (integer_nonzerop (res)
+			 ? EDGE_FALSE_VALUE : EDGE_TRUE_VALUE);
+	    }
 	}
-      else
+      else if (gswitch *swtch = safe_dyn_cast<gswitch *> (last))
+	if (predicate_path
+	    && !get_predicates_for_bb (bb).is_empty ())
+	  evaluate_control_stmt_using_entry_checks (swtch, *predicate_path,
+						    ignored_edge_flag,
+						    &ignored_edges);
+
+      /* Note that for the moment we do not account reachable conditions
+	 which are simplified to take a known edge as zero size nor
+	 are we accounting for the required addition of the versioning
+	 condition.  Those should cancel out conservatively.  */
+
+      FOR_EACH_EDGE (e, ei, bb->succs)
 	{
-	  found = i;
-	  break;
-	}
+	  basic_block dest = e->dest;
 
-      update_stmt (stmt);
-      i++;
+	  if (dest->loop_father == loop
+	      && !(dest->flags & reachable_flag)
+	      && !(e->flags & flags)
+	      && !ignored_edges.contains (e))
+	    {
+	      dest->flags |= reachable_flag;
+	      worklist.safe_push (dest);
+	      reachable.safe_push (dest);
+	    }
+	}
     }
 
-  if (num != 0)
-    {
-      basic_block *tos, *worklist;
+  /* Clear the flag from basic blocks.  */
+  while (!reachable.is_empty ())
+    reachable.pop ()->flags &= ~reachable_flag;
+}
 
-      /* When called recursively, first do a quick discovery
-	 of reachable bbs after the above changes and only
-	 consider conditions in still reachable bbs.  */
-      tos = worklist = XNEWVEC (basic_block, loop->num_nodes);
+/* Evaluate how many instruction will we have if we unswitch LOOP (with BBS)
+   based on PREDICATE predicate (using PREDICATE_PATH).  Store the
+   result in TRUE_SIZE and FALSE_SIZE.  */
 
-      for (i = 0; i < loop->num_nodes; i++)
-	bbs[i]->flags &= ~BB_REACHABLE;
+static void
+evaluate_loop_insns_for_predicate (class loop *loop,
+				   predicate_vector &predicate_path,
+				   unswitch_predicate *predicate,
+				   int ignored_edge_flag,
+				   unsigned *true_size, unsigned *false_size)
+{
+  unsigned size = 0;
+  auto sum_size = [&](basic_block bb) -> bool
+    { size += (uintptr_t)bb->aux; return false; };
+
+  add_predicate_to_path (predicate_path, predicate, true);
+  evaluate_bbs (loop, &predicate_path, ignored_edge_flag, sum_size);
+  predicate_path.pop ();
+  unsigned true_loop_cost = size;
+
+  size = 0;
+  add_predicate_to_path (predicate_path, predicate, false);
+  evaluate_bbs (loop, &predicate_path, ignored_edge_flag, sum_size);
+  predicate_path.pop ();
+  unsigned false_loop_cost = size;
+
+  *true_size = true_loop_cost;
+  *false_size = false_loop_cost;
+}
 
-      /* Start with marking header.  */
-      *tos++ = bbs[0];
-      bbs[0]->flags |= BB_REACHABLE;
+/* Unswitch single LOOP.  PREDICATE_PATH contains so far used predicates
+   for unswitching.  BUDGET is number of instruction for which we can increase
+   the loop and is updated when unswitching occurs.  */
 
-      /* Iterate: find everything reachable from what we've already seen
-	 within the same innermost loop.  Don't look through false edges
-	 if condition is always true or true edges if condition is
-	 always false.  */
-      while (tos != worklist)
+static bool
+tree_unswitch_single_loop (class loop *loop, dump_user_location_t loc,
+			   predicate_vector &predicate_path,
+			   unsigned loop_size, unsigned &budget,
+			   int ignored_edge_flag, bitmap handled)
+{
+  class loop *nloop;
+  bool changed = false;
+  unswitch_predicate *predicate = NULL;
+  basic_block predicate_bb = NULL;
+  unsigned true_size = 0, false_size = 0;
+
+  auto check_predicates = [&](basic_block bb) -> bool
+    {
+      for (auto pred : get_predicates_for_bb (bb))
 	{
-	  basic_block b = *--tos;
-	  edge e;
-	  edge_iterator ei;
-	  int flags = 0;
+	  if (bitmap_bit_p (handled, pred->num))
+	    continue;
 
-	  if (EDGE_COUNT (b->succs) == 2)
-	    {
-	      gimple *stmt = last_stmt (b);
-	      if (stmt
-		  && gimple_code (stmt) == GIMPLE_COND)
-		{
-		  gcond *cond_stmt = as_a <gcond *> (stmt);
-		  if (gimple_cond_true_p (cond_stmt))
-		    flags = EDGE_FALSE_VALUE;
-		  else if (gimple_cond_false_p (cond_stmt))
-		    flags = EDGE_TRUE_VALUE;
-		}
-	    }
+	  evaluate_loop_insns_for_predicate (loop, predicate_path,
+					     pred, ignored_edge_flag,
+					     &true_size, &false_size);
 
-	  FOR_EACH_EDGE (e, ei, b->succs)
+	  /* We'll get LOOP replaced with a simplified version according
+	     to PRED estimated to TRUE_SIZE and a copy simplified
+	     according to the inverted PRED estimated to FALSE_SIZE.  */
+	  if (true_size + false_size < budget + loop_size)
 	    {
-	      basic_block dest = e->dest;
-
-	      if (dest->loop_father == loop
-		  && !(dest->flags & BB_REACHABLE)
-		  && !(e->flags & flags))
-		{
-		  *tos++ = dest;
-		  dest->flags |= BB_REACHABLE;
-		}
+	      predicate = pred;
+	      predicate_bb = bb;
+
+	      /* There are cases where true_size and false_size add up to
+		 less than the original loop_size.  We do not want to
+		 grow the remaining budget because of that.  */
+	      if (true_size + false_size > loop_size)
+		budget -= (true_size + false_size - loop_size);
+
+	      /* FIXME: right now we select first candidate, but we can
+		 choose the cheapest or hottest one.  */
+	      return true;
 	    }
+	  else if (dump_enabled_p ())
+	    dump_printf_loc (MSG_NOTE, loc,
+			     "not unswitching condition, cost too big "
+			     "(%u insns copied to %u and %u)\n", loop_size,
+			     true_size, false_size);
 	}
+      return false;
+    };
+  /* Check predicates of reachable blocks.  */
+  evaluate_bbs (loop, NULL, ignored_edge_flag, check_predicates);
 
-      free (worklist);
+  if (predicate != NULL)
+    {
+      if (!dbg_cnt (loop_unswitch))
+	goto exit;
 
-      /* Find a bb to unswitch on.  */
-      for (; found < loop->num_nodes; found++)
-	if ((bbs[found]->flags & BB_REACHABLE)
-	    && (cond = tree_may_unswitch_on (bbs[found], loop)))
-	  break;
+      if (dump_enabled_p ())
+	{
+	  dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc,
+			   "unswitching loop %d on %qs with condition: %T\n",
+			   loop->num, predicate->switch_p ? "switch" : "if",
+			   predicate->condition);
+	  dump_printf_loc (MSG_NOTE, loc,
+			   "optimized sizes estimated to %u (true) "
+			   "and %u (false) from original size %u\n",
+			   true_size, false_size, loop_size);
+	}
 
-      if (found == loop->num_nodes)
+      bitmap_set_bit (handled, predicate->num);
+      initialize_original_copy_tables ();
+      /* Unswitch the loop on this condition.  */
+      nloop = tree_unswitch_loop (loop, EDGE_SUCC (predicate_bb,
+						   predicate->edge_index),
+				  predicate->condition);
+      if (!nloop)
 	{
-	  free (bbs);
-	  return changed;
+	  free_original_copy_tables ();
+	  goto exit;
 	}
-    }
 
-  if (dump_enabled_p ())
-    dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc,
-		     "Unswitching loop on condition: %G\n",
-		     last_stmt (bbs[found]));
+      /* Copy BB costs.  */
+      basic_block *bbs2 = get_loop_body (nloop);
+      for (unsigned i = 0; i < nloop->num_nodes; i++)
+	bbs2[i]->aux = get_bb_original (bbs2[i])->aux;
+      free (bbs2);
 
-  initialize_original_copy_tables ();
-  /* Unswitch the loop on this condition.  */
-  nloop = tree_unswitch_loop (loop, bbs[found], cond);
-  if (!nloop)
-    {
       free_original_copy_tables ();
-      free (bbs);
-      return changed;
-    }
 
-  /* Update the SSA form after unswitching.  */
-  update_ssa (TODO_update_ssa);
-  free_original_copy_tables ();
+      /* Update the SSA form after unswitching.  */
+      update_ssa (TODO_update_ssa);
+
+      /* Invoke itself on modified loops.  */
+      bitmap handled_copy = BITMAP_ALLOC (NULL);
+      bitmap_copy (handled_copy, handled);
+      add_predicate_to_path (predicate_path, predicate, false);
+      changed |= simplify_loop_version (nloop, predicate_path,
+					ignored_edge_flag, handled_copy);
+      tree_unswitch_single_loop (nloop, loc, predicate_path,
+				 false_size, budget,
+				 ignored_edge_flag, handled_copy);
+      predicate_path.pop ();
+      BITMAP_FREE (handled_copy);
+
+      /* FIXME: After unwinding above we have to reset all ->handled
+	 flags as otherwise we fail to realize unswitching opportunities
+	 in the below recursion.  See gcc.dg/loop-unswitch-16.c  */
+      add_predicate_to_path (predicate_path, predicate, true);
+      changed |= simplify_loop_version (loop, predicate_path,
+					ignored_edge_flag, handled);
+      tree_unswitch_single_loop (loop, loc, predicate_path,
+				 true_size, budget,
+				 ignored_edge_flag, handled);
+      predicate_path.pop ();
+      changed = true;
+    }
 
-  /* Invoke itself on modified loops.  */
-  tree_unswitch_single_loop (nloop, num + 1);
-  tree_unswitch_single_loop (loop, num + 1);
-  free (bbs);
-  return true;
+exit:
+  return changed;
 }
 
-/* Unswitch a LOOP w.r. to given basic block UNSWITCH_ON.  We only support
-   unswitching of innermost loops.  COND is the condition determining which
-   loop is entered -- the new loop is entered if COND is true.  Returns NULL
-   if impossible, new loop otherwise.  */
+/* Unswitch a LOOP w.r. to given EDGE_TRUE.  We only support unswitching of
+   innermost loops.  COND is the condition determining which loop is entered;
+   the new loop is entered if COND is true.  Returns NULL if impossible, new
+   loop otherwise.  */
 
 static class loop *
-tree_unswitch_loop (class loop *loop,
-		    basic_block unswitch_on, tree cond)
+tree_unswitch_loop (class loop *loop, edge edge_true, tree cond)
 {
-  profile_probability prob_true;
-  edge edge_true, edge_false;
-
   /* Some sanity checking.  */
-  gcc_assert (flow_bb_inside_loop_p (loop, unswitch_on));
-  gcc_assert (EDGE_COUNT (unswitch_on->succs) == 2);
+  gcc_assert (flow_bb_inside_loop_p (loop, edge_true->src));
+  gcc_assert (EDGE_COUNT (edge_true->src->succs) >= 2);
   gcc_assert (loop->inner == NULL);
 
-  extract_true_false_edges_from_block (unswitch_on, &edge_true, &edge_false);
-  prob_true = edge_true->probability;
+  profile_probability prob_true = edge_true->probability;
   return loop_version (loop, unshare_expr (cond),
 		       NULL, prob_true,
 		       prob_true.invert (),
@@ -1010,6 +1539,57 @@  check_exit_phi (class loop *loop)
   return true;
 }
 
+/* Remove all dead cases from switches that are unswitched.  */
+
+static void
+clean_up_after_unswitching (int ignored_edge_flag)
+{
+  basic_block bb;
+  edge e;
+  edge_iterator ei;
+
+  FOR_EACH_BB_FN (bb, cfun)
+    {
+      gswitch *stmt= safe_dyn_cast <gswitch *> (last_stmt (bb));
+      if (stmt && !CONSTANT_CLASS_P (gimple_switch_index (stmt)))
+	{
+	  unsigned nlabels = gimple_switch_num_labels (stmt);
+	  unsigned index = 1;
+	  tree lab = gimple_switch_default_label (stmt);
+	  edge default_e = find_edge (gimple_bb (stmt),
+				      label_to_block (cfun, CASE_LABEL (lab)));
+	  for (unsigned i = 1; i < nlabels; ++i)
+	    {
+	      tree lab = gimple_switch_label (stmt, i);
+	      basic_block dest = label_to_block (cfun, CASE_LABEL (lab));
+	      edge e = find_edge (gimple_bb (stmt), dest);
+	      if (e == NULL)
+		; /* The edge is already removed.  */
+	      else if (e->flags & ignored_edge_flag)
+		{
+		  /* We may not remove the default label so we also have
+		     to preserve its edge.  But we can remove the
+		     non-default CASE sharing the edge.  */
+		  if (e != default_e)
+		    remove_edge (e);
+		}
+	      else
+		{
+		  gimple_switch_set_label (stmt, index, lab);
+		  ++index;
+		}
+	    }
+
+	  if (index != nlabels)
+	    gimple_switch_set_num_labels (stmt, index);
+	}
+
+      /* Clean up the ignored_edge_flag from edges.  */
+      FOR_EACH_EDGE (e, ei, bb->succs)
+	e->flags &= ~ignored_edge_flag;
+    }
+}
+
 /* Loop unswitching pass.  */
 
 namespace {
@@ -1046,7 +1626,7 @@  pass_tree_unswitch::execute (function *fun)
   if (number_of_loops (fun) <= 1)
     return 0;
 
-  return tree_ssa_unswitch_loops ();
+  return tree_ssa_unswitch_loops (fun);
 }
 
 } // anon namespace