diff mbox series

[11/12] mode-switching: Add a target-configurable confluence operator

Message ID mptbkc86nmt.fsf@arm.com
State New
Headers show
Series Tweaks and extensions to the mode-switching pass | expand

Commit Message

Richard Sandiford Nov. 5, 2023, 6:50 p.m. UTC
The mode-switching pass assumed that all of an entity's modes
were mutually exclusive.  However, the upcoming SME changes
have an entity with some overlapping modes, so that there is
sometimes a "superunion" mode that contains two given modes.
We can use this relationship to pass something more helpful than
"don't know" to the emit hook.

This patch adds a new hook that targets can use to specify
a mode confluence operator.

With mutually exclusive modes, it's possible to compute a block's
incoming and outgoing modes by looking at its availability sets.
With the confluence operator, we instead need to solve a full
dataflow problem.

However, when emitting a mode transition, the upcoming SME use of
mode-switching benefits from having as much information as possible
about the starting mode.  Calculating this information is definitely
worth the compile time.

The dataflow problem is written to work before and after the LCM
problem has been solved.  A later patch makes use of this.

While there (since git blame would ping me for the reindented code),
I used a lambda to avoid the cut-&-pasted loops.

gcc/
	* target.def (mode_switching.confluence): New hook.
	* doc/tm.texi (TARGET_MODE_CONFLUENCE): New @hook.
	* doc/tm.texi.in: Regenerate.
	* mode-switching.cc (confluence_info): New variable.
	(mode_confluence, forward_confluence_n, forward_transfer): New
	functions.
	(optimize_mode_switching): Use them to calculate mode_in when
	TARGET_MODE_CONFLUENCE is defined.
---
 gcc/doc/tm.texi       |  16 ++++
 gcc/doc/tm.texi.in    |   2 +
 gcc/mode-switching.cc | 179 +++++++++++++++++++++++++++++++++++-------
 gcc/target.def        |  17 ++++
 4 files changed, 186 insertions(+), 28 deletions(-)

Comments

Jeff Law Nov. 7, 2023, 3:04 a.m. UTC | #1
On 11/5/23 11:50, Richard Sandiford wrote:
> The mode-switching pass assumed that all of an entity's modes
> were mutually exclusive.  However, the upcoming SME changes
> have an entity with some overlapping modes, so that there is
> sometimes a "superunion" mode that contains two given modes.
> We can use this relationship to pass something more helpful than
> "don't know" to the emit hook.
> 
> This patch adds a new hook that targets can use to specify
> a mode confluence operator.
> 
> With mutually exclusive modes, it's possible to compute a block's
> incoming and outgoing modes by looking at its availability sets.
> With the confluence operator, we instead need to solve a full
> dataflow problem.
> 
> However, when emitting a mode transition, the upcoming SME use of
> mode-switching benefits from having as much information as possible
> about the starting mode.  Calculating this information is definitely
> worth the compile time.
> 
> The dataflow problem is written to work before and after the LCM
> problem has been solved.  A later patch makes use of this.
> 
> While there (since git blame would ping me for the reindented code),
> I used a lambda to avoid the cut-&-pasted loops.
> 
> gcc/
> 	* target.def (mode_switching.confluence): New hook.
> 	* doc/tm.texi (TARGET_MODE_CONFLUENCE): New @hook.
> 	* doc/tm.texi.in: Regenerate.
> 	* mode-switching.cc (confluence_info): New variable.
> 	(mode_confluence, forward_confluence_n, forward_transfer): New
> 	functions.
> 	(optimize_mode_switching): Use them to calculate mode_in when
> 	TARGET_MODE_CONFLUENCE is defined.
OK.  There's certain similarities between this and the compatible states 
we can use to reduce vsetvl instructions in RV-V.   I wonder if Juzhe or 
Lehua could utilize this and do less custom optimization code in the RV 
backend.

jeff
Richard Sandiford Nov. 11, 2023, 3:54 p.m. UTC | #2
Jeff Law <jeffreyalaw@gmail.com> writes:
> On 11/5/23 11:50, Richard Sandiford wrote:
>> The mode-switching pass assumed that all of an entity's modes
>> were mutually exclusive.  However, the upcoming SME changes
>> have an entity with some overlapping modes, so that there is
>> sometimes a "superunion" mode that contains two given modes.
>> We can use this relationship to pass something more helpful than
>> "don't know" to the emit hook.
>> 
>> This patch adds a new hook that targets can use to specify
>> a mode confluence operator.
>> 
>> With mutually exclusive modes, it's possible to compute a block's
>> incoming and outgoing modes by looking at its availability sets.
>> With the confluence operator, we instead need to solve a full
>> dataflow problem.
>> 
>> However, when emitting a mode transition, the upcoming SME use of
>> mode-switching benefits from having as much information as possible
>> about the starting mode.  Calculating this information is definitely
>> worth the compile time.
>> 
>> The dataflow problem is written to work before and after the LCM
>> problem has been solved.  A later patch makes use of this.
>> 
>> While there (since git blame would ping me for the reindented code),
>> I used a lambda to avoid the cut-&-pasted loops.
>> 
>> gcc/
>> 	* target.def (mode_switching.confluence): New hook.
>> 	* doc/tm.texi (TARGET_MODE_CONFLUENCE): New @hook.
>> 	* doc/tm.texi.in: Regenerate.
>> 	* mode-switching.cc (confluence_info): New variable.
>> 	(mode_confluence, forward_confluence_n, forward_transfer): New
>> 	functions.
>> 	(optimize_mode_switching): Use them to calculate mode_in when
>> 	TARGET_MODE_CONFLUENCE is defined.
> OK.  There's certain similarities between this and the compatible states 
> we can use to reduce vsetvl instructions in RV-V.   I wonder if Juzhe or 
> Lehua could utilize this and do less custom optimization code in the RV 
> backend.

Here's an update based on what you pointed out in 10/12.  The change
from last time is to add:

  if (targetm.mode_switching.backprop)
    clear_aux_for_edges ();

before the main loop.  Tested as before.

Thanks,
Richard


gcc/
	* target.def (mode_switching.confluence): New hook.
	* doc/tm.texi (TARGET_MODE_CONFLUENCE): New @hook.
	* doc/tm.texi.in: Regenerate.
	* mode-switching.cc (confluence_info): New variable.
	(mode_confluence, forward_confluence_n, forward_transfer): New
	functions.
	(optimize_mode_switching): Use them to calculate mode_in when
	TARGET_MODE_CONFLUENCE is defined.
---
 gcc/doc/tm.texi       |  16 ++++
 gcc/doc/tm.texi.in    |   2 +
 gcc/mode-switching.cc | 182 +++++++++++++++++++++++++++++++++++-------
 gcc/target.def        |  17 ++++
 4 files changed, 189 insertions(+), 28 deletions(-)

diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index b730b5bf658..cd346538fe2 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -10440,6 +10440,22 @@ the number of modes if it does not know what mode @var{entity} has after
 Not defining the hook is equivalent to returning @var{mode}.
 @end deftypefn
 
+@deftypefn {Target Hook} int TARGET_MODE_CONFLUENCE (int @var{entity}, int @var{mode1}, int @var{mode2})
+By default, the mode-switching pass assumes that a given entity's modes
+are mutually exclusive.  This means that the pass can only tell
+@code{TARGET_MODE_EMIT} about an entity's previous mode if all
+incoming paths of execution leave the entity in the same state.
+
+However, some entities might have overlapping, non-exclusive modes,
+so that it is sometimes possible to represent ``mode @var{mode1} or mode
+@var{mode2}'' with something more specific than ``mode not known''.
+If this is true for at least one entity, you should define this hook
+and make it return a mode that includes @var{mode1} and @var{mode2}
+as possibilities.  (The mode can include other possibilities too.)
+The hook should return the number of modes if no suitable mode exists
+for the given arguments.
+@end deftypefn
+
 @deftypefn {Target Hook} int TARGET_MODE_ENTRY (int @var{entity})
 If this hook is defined, it is evaluated for every @var{entity} that
 needs mode switching.  It should return the mode that @var{entity} is
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index 5360c1bb2d8..ae23241ea1c 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -6975,6 +6975,8 @@ mode or ``no mode'', depending on context.
 
 @hook TARGET_MODE_AFTER
 
+@hook TARGET_MODE_CONFLUENCE
+
 @hook TARGET_MODE_ENTRY
 
 @hook TARGET_MODE_EXIT
diff --git a/gcc/mode-switching.cc b/gcc/mode-switching.cc
index 6b5661131e3..58bc1934e81 100644
--- a/gcc/mode-switching.cc
+++ b/gcc/mode-switching.cc
@@ -485,6 +485,101 @@ create_pre_exit (int n_entities, int *entity_map, const int *num_modes)
   return pre_exit;
 }
 
+/* Return the confluence of modes MODE1 and MODE2 for entity ENTITY,
+   using NO_MODE to represent an unknown mode if nothing more precise
+   is available.  */
+
+int
+mode_confluence (int entity, int mode1, int mode2, int no_mode)
+{
+  if (mode1 == mode2)
+    return mode1;
+
+  if (mode1 != no_mode
+      && mode2 != no_mode
+      && targetm.mode_switching.confluence)
+    return targetm.mode_switching.confluence (entity, mode1, mode2);
+
+  return no_mode;
+}
+
+/* Information for the dataflow problems below.  */
+struct
+{
+  /* Information about each basic block, indexed by block id.  */
+  struct bb_info *bb_info;
+
+  /* The entity that we're processing.  */
+  int entity;
+
+  /* The number of modes defined for the entity, and thus the identifier
+     of the "don't know" mode.  */
+  int no_mode;
+} confluence_info;
+
+/* Propagate information about any mode change on edge E to the
+   destination block's mode_in.  Return true if something changed.
+
+   The mode_in and mode_out fields use no_mode + 1 to mean "not yet set".  */
+
+static bool
+forward_confluence_n (edge e)
+{
+  /* The entry and exit blocks have no useful mode information.  */
+  if (e->src->index == ENTRY_BLOCK || e->dest->index == EXIT_BLOCK)
+    return false;
+
+  /* We don't control mode changes across abnormal edges.  */
+  if (e->flags & EDGE_ABNORMAL)
+    return false;
+
+  /* E->aux is nonzero if we have computed the LCM problem and scheduled
+     E to change the mode to E->aux - 1.  Otherwise model the change
+     from the source to the destination.  */
+  struct bb_info *bb_info = confluence_info.bb_info;
+  int no_mode = confluence_info.no_mode;
+  int src_mode = bb_info[e->src->index].mode_out;
+  if (e->aux)
+    src_mode = (int) (intptr_t) e->aux - 1;
+  if (src_mode == no_mode + 1)
+    return false;
+
+  int dest_mode = bb_info[e->dest->index].mode_in;
+  if (dest_mode == no_mode + 1)
+    {
+      bb_info[e->dest->index].mode_in = src_mode;
+      return true;
+    }
+
+  int entity = confluence_info.entity;
+  int new_mode = mode_confluence (entity, src_mode, dest_mode, no_mode);
+  if (dest_mode == new_mode)
+    return false;
+
+  bb_info[e->dest->index].mode_in = new_mode;
+  return true;
+}
+
+/* Update block BB_INDEX's mode_out based on its mode_in.  Return true if
+   something changed.  */
+
+static bool
+forward_transfer (int bb_index)
+{
+  /* The entry and exit blocks have no useful mode information.  */
+  if (bb_index == ENTRY_BLOCK || bb_index == EXIT_BLOCK)
+    return false;
+
+  /* Only propagate through a block if the entity is transparent.  */
+  struct bb_info *bb_info = confluence_info.bb_info;
+  if (bb_info[bb_index].computing != confluence_info.no_mode
+      || bb_info[bb_index].mode_out == bb_info[bb_index].mode_in)
+    return false;
+
+  bb_info[bb_index].mode_out = bb_info[bb_index].mode_in;
+  return true;
+}
+
 /* Find all insns that need a particular mode setting, and insert the
    necessary mode switches.  Return true if we did work.  */
 
@@ -568,6 +663,42 @@ optimize_mode_switching (void)
 
   auto_sbitmap transp_all (last_basic_block_for_fn (cfun));
 
+  auto_bitmap blocks;
+
+  /* Forward-propagate mode information through blocks where the entity
+     is transparent, so that mode_in describes the mode on entry to each
+     block and mode_out describes the mode on exit from each block.  */
+  auto forwprop_mode_info = [&](struct bb_info *info,
+				int entity, int no_mode)
+    {
+      /* Use no_mode + 1 to mean "not yet set".  */
+      FOR_EACH_BB_FN (bb, cfun)
+	{
+	  if (bb_has_abnormal_pred (bb))
+	    info[bb->index].mode_in = info[bb->index].seginfo->mode;
+	  else
+	    info[bb->index].mode_in = no_mode + 1;
+	  if (info[bb->index].computing != no_mode)
+	    info[bb->index].mode_out = info[bb->index].computing;
+	  else
+	    info[bb->index].mode_out = no_mode + 1;
+	}
+
+      confluence_info.bb_info = info;
+      confluence_info.entity = entity;
+      confluence_info.no_mode = no_mode;
+
+      bitmap_set_range (blocks, 0, last_basic_block_for_fn (cfun));
+      df_simple_dataflow (DF_FORWARD, NULL, NULL, forward_confluence_n,
+			  forward_transfer, blocks,
+			  df_get_postorder (DF_FORWARD),
+			  df_get_n_blocks (DF_FORWARD));
+
+    };
+
+  if (targetm.mode_switching.backprop)
+    clear_aux_for_edges ();
+
   for (j = n_entities - 1; j >= 0; j--)
     {
       int e = entity_map[j];
@@ -721,6 +852,7 @@ optimize_mode_switching (void)
   for (j = n_entities - 1; j >= 0; j--)
     {
       int no_mode = num_modes[entity_map[j]];
+      struct bb_info *info = bb_info[j];
 
       /* Insert all mode sets that have been inserted by lcm.  */
 
@@ -741,39 +873,33 @@ optimize_mode_switching (void)
 	    }
 	}
 
+      /* mode_in and mode_out can be calculated directly from avin and
+	 avout if all the modes are mutually exclusive.  Use the target-
+	 provided confluence function otherwise.  */
+      if (targetm.mode_switching.confluence)
+	forwprop_mode_info (info, entity_map[j], no_mode);
+
       FOR_EACH_BB_FN (bb, cfun)
 	{
-	  struct bb_info *info = bb_info[j];
-	  int last_mode = no_mode;
-
-	  /* intialize mode in availability for bb.  */
-	  for (i = 0; i < no_mode; i++)
-	    if (mode_bit_p (avout[bb->index], j, i))
-	      {
-		if (last_mode == no_mode)
-		  last_mode = i;
-		if (last_mode != i)
+	  auto modes_confluence = [&](sbitmap *av)
+	    {
+	      for (int i = 0; i < no_mode; ++i)
+		if (mode_bit_p (av[bb->index], j, i))
 		  {
-		    last_mode = no_mode;
-		    break;
+		    for (int i2 = i + 1; i2 < no_mode; ++i2)
+		      if (mode_bit_p (av[bb->index], j, i2))
+			return no_mode;
+		    return i;
 		  }
-	      }
-	  info[bb->index].mode_out = last_mode;
+	      return no_mode;
+	    };
 
-	  /* intialize mode out availability for bb.  */
-	  last_mode = no_mode;
-	  for (i = 0; i < no_mode; i++)
-	    if (mode_bit_p (avin[bb->index], j, i))
-	      {
-		if (last_mode == no_mode)
-		  last_mode = i;
-		if (last_mode != i)
-		  {
-		    last_mode = no_mode;
-		    break;
-		  }
-	      }
-	  info[bb->index].mode_in = last_mode;
+	  /* intialize mode in/out availability for bb.  */
+	  if (!targetm.mode_switching.confluence)
+	    {
+	      info[bb->index].mode_out = modes_confluence (avout);
+	      info[bb->index].mode_in = modes_confluence (avin);
+	    }
 
 	  for (i = 0; i < no_mode; i++)
 	    if (mode_bit_p (del[bb->index], j, i))
diff --git a/gcc/target.def b/gcc/target.def
index 9b14c037d3f..b08ede692f1 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -7053,6 +7053,23 @@ the number of modes if it does not know what mode @var{entity} has after\n\
 Not defining the hook is equivalent to returning @var{mode}.",
  int, (int entity, int mode, rtx_insn *insn, HARD_REG_SET regs_live), NULL)
 
+DEFHOOK
+(confluence,
+ "By default, the mode-switching pass assumes that a given entity's modes\n\
+are mutually exclusive.  This means that the pass can only tell\n\
+@code{TARGET_MODE_EMIT} about an entity's previous mode if all\n\
+incoming paths of execution leave the entity in the same state.\n\
+\n\
+However, some entities might have overlapping, non-exclusive modes,\n\
+so that it is sometimes possible to represent ``mode @var{mode1} or mode\n\
+@var{mode2}'' with something more specific than ``mode not known''.\n\
+If this is true for at least one entity, you should define this hook\n\
+and make it return a mode that includes @var{mode1} and @var{mode2}\n\
+as possibilities.  (The mode can include other possibilities too.)\n\
+The hook should return the number of modes if no suitable mode exists\n\
+for the given arguments.",
+ int, (int entity, int mode1, int mode2), NULL)
+
 DEFHOOK
 (entry,
  "If this hook is defined, it is evaluated for every @var{entity} that\n\
Jeff Law Nov. 11, 2023, 4:19 p.m. UTC | #3
On 11/11/23 08:54, Richard Sandiford wrote:
> Jeff Law <jeffreyalaw@gmail.com> writes:
>> On 11/5/23 11:50, Richard Sandiford wrote:
>>> The mode-switching pass assumed that all of an entity's modes
>>> were mutually exclusive.  However, the upcoming SME changes
>>> have an entity with some overlapping modes, so that there is
>>> sometimes a "superunion" mode that contains two given modes.
>>> We can use this relationship to pass something more helpful than
>>> "don't know" to the emit hook.
>>>
>>> This patch adds a new hook that targets can use to specify
>>> a mode confluence operator.
>>>
>>> With mutually exclusive modes, it's possible to compute a block's
>>> incoming and outgoing modes by looking at its availability sets.
>>> With the confluence operator, we instead need to solve a full
>>> dataflow problem.
>>>
>>> However, when emitting a mode transition, the upcoming SME use of
>>> mode-switching benefits from having as much information as possible
>>> about the starting mode.  Calculating this information is definitely
>>> worth the compile time.
>>>
>>> The dataflow problem is written to work before and after the LCM
>>> problem has been solved.  A later patch makes use of this.
>>>
>>> While there (since git blame would ping me for the reindented code),
>>> I used a lambda to avoid the cut-&-pasted loops.
>>>
>>> gcc/
>>> 	* target.def (mode_switching.confluence): New hook.
>>> 	* doc/tm.texi (TARGET_MODE_CONFLUENCE): New @hook.
>>> 	* doc/tm.texi.in: Regenerate.
>>> 	* mode-switching.cc (confluence_info): New variable.
>>> 	(mode_confluence, forward_confluence_n, forward_transfer): New
>>> 	functions.
>>> 	(optimize_mode_switching): Use them to calculate mode_in when
>>> 	TARGET_MODE_CONFLUENCE is defined.
>> OK.  There's certain similarities between this and the compatible states
>> we can use to reduce vsetvl instructions in RV-V.   I wonder if Juzhe or
>> Lehua could utilize this and do less custom optimization code in the RV
>> backend.
> 
> Here's an update based on what you pointed out in 10/12.  The change
> from last time is to add:
> 
>    if (targetm.mode_switching.backprop)
>      clear_aux_for_edges ();
> 
> before the main loop.  Tested as before.
> 
> Thanks,
> Richard
> 
> 
> gcc/
> 	* target.def (mode_switching.confluence): New hook.
> 	* doc/tm.texi (TARGET_MODE_CONFLUENCE): New @hook.
> 	* doc/tm.texi.in: Regenerate.
> 	* mode-switching.cc (confluence_info): New variable.
> 	(mode_confluence, forward_confluence_n, forward_transfer): New
> 	functions.
> 	(optimize_mode_switching): Use them to calculate mode_in when
> 	TARGET_MODE_CONFLUENCE is defined.
OK.  That's the whole set, right?

jeff
Richard Sandiford Nov. 11, 2023, 5:29 p.m. UTC | #4
Jeff Law <jeffreyalaw@gmail.com> writes:
> On 11/11/23 08:54, Richard Sandiford wrote:
>> Jeff Law <jeffreyalaw@gmail.com> writes:
>>> On 11/5/23 11:50, Richard Sandiford wrote:
>>>> The mode-switching pass assumed that all of an entity's modes
>>>> were mutually exclusive.  However, the upcoming SME changes
>>>> have an entity with some overlapping modes, so that there is
>>>> sometimes a "superunion" mode that contains two given modes.
>>>> We can use this relationship to pass something more helpful than
>>>> "don't know" to the emit hook.
>>>>
>>>> This patch adds a new hook that targets can use to specify
>>>> a mode confluence operator.
>>>>
>>>> With mutually exclusive modes, it's possible to compute a block's
>>>> incoming and outgoing modes by looking at its availability sets.
>>>> With the confluence operator, we instead need to solve a full
>>>> dataflow problem.
>>>>
>>>> However, when emitting a mode transition, the upcoming SME use of
>>>> mode-switching benefits from having as much information as possible
>>>> about the starting mode.  Calculating this information is definitely
>>>> worth the compile time.
>>>>
>>>> The dataflow problem is written to work before and after the LCM
>>>> problem has been solved.  A later patch makes use of this.
>>>>
>>>> While there (since git blame would ping me for the reindented code),
>>>> I used a lambda to avoid the cut-&-pasted loops.
>>>>
>>>> gcc/
>>>> 	* target.def (mode_switching.confluence): New hook.
>>>> 	* doc/tm.texi (TARGET_MODE_CONFLUENCE): New @hook.
>>>> 	* doc/tm.texi.in: Regenerate.
>>>> 	* mode-switching.cc (confluence_info): New variable.
>>>> 	(mode_confluence, forward_confluence_n, forward_transfer): New
>>>> 	functions.
>>>> 	(optimize_mode_switching): Use them to calculate mode_in when
>>>> 	TARGET_MODE_CONFLUENCE is defined.
>>> OK.  There's certain similarities between this and the compatible states
>>> we can use to reduce vsetvl instructions in RV-V.   I wonder if Juzhe or
>>> Lehua could utilize this and do less custom optimization code in the RV
>>> backend.
>> 
>> Here's an update based on what you pointed out in 10/12.  The change
>> from last time is to add:
>> 
>>    if (targetm.mode_switching.backprop)
>>      clear_aux_for_edges ();
>> 
>> before the main loop.  Tested as before.
>> 
>> Thanks,
>> Richard
>> 
>> 
>> gcc/
>> 	* target.def (mode_switching.confluence): New hook.
>> 	* doc/tm.texi (TARGET_MODE_CONFLUENCE): New @hook.
>> 	* doc/tm.texi.in: Regenerate.
>> 	* mode-switching.cc (confluence_info): New variable.
>> 	(mode_confluence, forward_confluence_n, forward_transfer): New
>> 	functions.
>> 	(optimize_mode_switching): Use them to calculate mode_in when
>> 	TARGET_MODE_CONFLUENCE is defined.
> OK.  That's the whole set, right?

Yeah, that's everything, thanks.  I've now pushed the series.

Richard
diff mbox series

Patch

diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index b730b5bf658..cd346538fe2 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -10440,6 +10440,22 @@  the number of modes if it does not know what mode @var{entity} has after
 Not defining the hook is equivalent to returning @var{mode}.
 @end deftypefn
 
+@deftypefn {Target Hook} int TARGET_MODE_CONFLUENCE (int @var{entity}, int @var{mode1}, int @var{mode2})
+By default, the mode-switching pass assumes that a given entity's modes
+are mutually exclusive.  This means that the pass can only tell
+@code{TARGET_MODE_EMIT} about an entity's previous mode if all
+incoming paths of execution leave the entity in the same state.
+
+However, some entities might have overlapping, non-exclusive modes,
+so that it is sometimes possible to represent ``mode @var{mode1} or mode
+@var{mode2}'' with something more specific than ``mode not known''.
+If this is true for at least one entity, you should define this hook
+and make it return a mode that includes @var{mode1} and @var{mode2}
+as possibilities.  (The mode can include other possibilities too.)
+The hook should return the number of modes if no suitable mode exists
+for the given arguments.
+@end deftypefn
+
 @deftypefn {Target Hook} int TARGET_MODE_ENTRY (int @var{entity})
 If this hook is defined, it is evaluated for every @var{entity} that
 needs mode switching.  It should return the mode that @var{entity} is
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index 5360c1bb2d8..ae23241ea1c 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -6975,6 +6975,8 @@  mode or ``no mode'', depending on context.
 
 @hook TARGET_MODE_AFTER
 
+@hook TARGET_MODE_CONFLUENCE
+
 @hook TARGET_MODE_ENTRY
 
 @hook TARGET_MODE_EXIT
diff --git a/gcc/mode-switching.cc b/gcc/mode-switching.cc
index 1815b397dd0..87b23d2c050 100644
--- a/gcc/mode-switching.cc
+++ b/gcc/mode-switching.cc
@@ -485,6 +485,101 @@  create_pre_exit (int n_entities, int *entity_map, const int *num_modes)
   return pre_exit;
 }
 
+/* Return the confluence of modes MODE1 and MODE2 for entity ENTITY,
+   using NO_MODE to represent an unknown mode if nothing more precise
+   is available.  */
+
+int
+mode_confluence (int entity, int mode1, int mode2, int no_mode)
+{
+  if (mode1 == mode2)
+    return mode1;
+
+  if (mode1 != no_mode
+      && mode2 != no_mode
+      && targetm.mode_switching.confluence)
+    return targetm.mode_switching.confluence (entity, mode1, mode2);
+
+  return no_mode;
+}
+
+/* Information for the dataflow problems below.  */
+struct
+{
+  /* Information about each basic block, indexed by block id.  */
+  struct bb_info *bb_info;
+
+  /* The entity that we're processing.  */
+  int entity;
+
+  /* The number of modes defined for the entity, and thus the identifier
+     of the "don't know" mode.  */
+  int no_mode;
+} confluence_info;
+
+/* Propagate information about any mode change on edge E to the
+   destination block's mode_in.  Return true if something changed.
+
+   The mode_in and mode_out fields use no_mode + 1 to mean "not yet set".  */
+
+static bool
+forward_confluence_n (edge e)
+{
+  /* The entry and exit blocks have no useful mode information.  */
+  if (e->src->index == ENTRY_BLOCK || e->dest->index == EXIT_BLOCK)
+    return false;
+
+  /* We don't control mode changes across abnormal edges.  */
+  if (e->flags & EDGE_ABNORMAL)
+    return false;
+
+  /* E->aux is nonzero if we have computed the LCM problem and scheduled
+     E to change the mode to E->aux - 1.  Otherwise model the change
+     from the source to the destination.  */
+  struct bb_info *bb_info = confluence_info.bb_info;
+  int no_mode = confluence_info.no_mode;
+  int src_mode = bb_info[e->src->index].mode_out;
+  if (e->aux)
+    src_mode = (int) (intptr_t) e->aux - 1;
+  if (src_mode == no_mode + 1)
+    return false;
+
+  int dest_mode = bb_info[e->dest->index].mode_in;
+  if (dest_mode == no_mode + 1)
+    {
+      bb_info[e->dest->index].mode_in = src_mode;
+      return true;
+    }
+
+  int entity = confluence_info.entity;
+  int new_mode = mode_confluence (entity, src_mode, dest_mode, no_mode);
+  if (dest_mode == new_mode)
+    return false;
+
+  bb_info[e->dest->index].mode_in = new_mode;
+  return true;
+}
+
+/* Update block BB_INDEX's mode_out based on its mode_in.  Return true if
+   something changed.  */
+
+static bool
+forward_transfer (int bb_index)
+{
+  /* The entry and exit blocks have no useful mode information.  */
+  if (bb_index == ENTRY_BLOCK || bb_index == EXIT_BLOCK)
+    return false;
+
+  /* Only propagate through a block if the entity is transparent.  */
+  struct bb_info *bb_info = confluence_info.bb_info;
+  if (bb_info[bb_index].computing != confluence_info.no_mode
+      || bb_info[bb_index].mode_out == bb_info[bb_index].mode_in)
+    return false;
+
+  bb_info[bb_index].mode_out = bb_info[bb_index].mode_in;
+  return true;
+}
+
 /* Find all insns that need a particular mode setting, and insert the
    necessary mode switches.  Return true if we did work.  */
 
@@ -568,6 +663,39 @@  optimize_mode_switching (void)
 
   auto_sbitmap transp_all (last_basic_block_for_fn (cfun));
 
+  auto_bitmap blocks;
+
+  /* Forward-propagate mode information through blocks where the entity
+     is transparent, so that mode_in describes the mode on entry to each
+     block and mode_out describes the mode on exit from each block.  */
+  auto forwprop_mode_info = [&](struct bb_info *info,
+				int entity, int no_mode)
+    {
+      /* Use no_mode + 1 to mean "not yet set".  */
+      FOR_EACH_BB_FN (bb, cfun)
+	{
+	  if (bb_has_abnormal_pred (bb))
+	    info[bb->index].mode_in = info[bb->index].seginfo->mode;
+	  else
+	    info[bb->index].mode_in = no_mode + 1;
+	  if (info[bb->index].computing != no_mode)
+	    info[bb->index].mode_out = info[bb->index].computing;
+	  else
+	    info[bb->index].mode_out = no_mode + 1;
+	}
+
+      confluence_info.bb_info = info;
+      confluence_info.entity = entity;
+      confluence_info.no_mode = no_mode;
+
+      bitmap_set_range (blocks, 0, last_basic_block_for_fn (cfun));
+      df_simple_dataflow (DF_FORWARD, NULL, NULL, forward_confluence_n,
+			  forward_transfer, blocks,
+			  df_get_postorder (DF_FORWARD),
+			  df_get_n_blocks (DF_FORWARD));
+
+    };
+
   for (j = n_entities - 1; j >= 0; j--)
     {
       int e = entity_map[j];
@@ -721,6 +849,7 @@  optimize_mode_switching (void)
   for (j = n_entities - 1; j >= 0; j--)
     {
       int no_mode = num_modes[entity_map[j]];
+      struct bb_info *info = bb_info[j];
 
       /* Insert all mode sets that have been inserted by lcm.  */
 
@@ -739,39 +868,33 @@  optimize_mode_switching (void)
 	    }
 	}
 
+      /* mode_in and mode_out can be calculated directly from avin and
+	 avout if all the modes are mutually exclusive.  Use the target-
+	 provided confluence function otherwise.  */
+      if (targetm.mode_switching.confluence)
+	forwprop_mode_info (info, entity_map[j], no_mode);
+
       FOR_EACH_BB_FN (bb, cfun)
 	{
-	  struct bb_info *info = bb_info[j];
-	  int last_mode = no_mode;
-
-	  /* intialize mode in availability for bb.  */
-	  for (i = 0; i < no_mode; i++)
-	    if (mode_bit_p (avout[bb->index], j, i))
-	      {
-		if (last_mode == no_mode)
-		  last_mode = i;
-		if (last_mode != i)
+	  auto modes_confluence = [&](sbitmap *av)
+	    {
+	      for (int i = 0; i < no_mode; ++i)
+		if (mode_bit_p (av[bb->index], j, i))
 		  {
-		    last_mode = no_mode;
-		    break;
+		    for (int i2 = i + 1; i2 < no_mode; ++i2)
+		      if (mode_bit_p (av[bb->index], j, i2))
+			return no_mode;
+		    return i;
 		  }
-	      }
-	  info[bb->index].mode_out = last_mode;
+	      return no_mode;
+	    };
 
-	  /* intialize mode out availability for bb.  */
-	  last_mode = no_mode;
-	  for (i = 0; i < no_mode; i++)
-	    if (mode_bit_p (avin[bb->index], j, i))
-	      {
-		if (last_mode == no_mode)
-		  last_mode = i;
-		if (last_mode != i)
-		  {
-		    last_mode = no_mode;
-		    break;
-		  }
-	      }
-	  info[bb->index].mode_in = last_mode;
+	  /* intialize mode in/out availability for bb.  */
+	  if (!targetm.mode_switching.confluence)
+	    {
+	      info[bb->index].mode_out = modes_confluence (avout);
+	      info[bb->index].mode_in = modes_confluence (avin);
+	    }
 
 	  for (i = 0; i < no_mode; i++)
 	    if (mode_bit_p (del[bb->index], j, i))
diff --git a/gcc/target.def b/gcc/target.def
index 9b14c037d3f..b08ede692f1 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -7053,6 +7053,23 @@  the number of modes if it does not know what mode @var{entity} has after\n\
 Not defining the hook is equivalent to returning @var{mode}.",
  int, (int entity, int mode, rtx_insn *insn, HARD_REG_SET regs_live), NULL)
 
+DEFHOOK
+(confluence,
+ "By default, the mode-switching pass assumes that a given entity's modes\n\
+are mutually exclusive.  This means that the pass can only tell\n\
+@code{TARGET_MODE_EMIT} about an entity's previous mode if all\n\
+incoming paths of execution leave the entity in the same state.\n\
+\n\
+However, some entities might have overlapping, non-exclusive modes,\n\
+so that it is sometimes possible to represent ``mode @var{mode1} or mode\n\
+@var{mode2}'' with something more specific than ``mode not known''.\n\
+If this is true for at least one entity, you should define this hook\n\
+and make it return a mode that includes @var{mode1} and @var{mode2}\n\
+as possibilities.  (The mode can include other possibilities too.)\n\
+The hook should return the number of modes if no suitable mode exists\n\
+for the given arguments.",
+ int, (int entity, int mode1, int mode2), NULL)
+
 DEFHOOK
 (entry,
  "If this hook is defined, it is evaluated for every @var{entity} that\n\