Jeff Law <[email protected]> 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\
--
2.25.1