Loop optimiser upgrade (Was RFC: BB duplication code)
Michael Hayes
m.hayes@elec.canterbury.ac.nz
Fri Sep 7 20:34:00 GMT 2001
Jan Hubicka writes:
> > I'd say strip the loop notes. You don't really lose any information,
> > in that correct information can always be obtained from the shape of
> > the CFG, via flow_loop_nodes_find.
> Agreed - anyway an loop optimizer is still an barrier for my effort.
> Until it is converted to use CFG, we can't make flow info survive.
I agree that the loop notes should be weeded out. The one useful loop
note is the VTOP note that indicates if the loop exit code has been
duplicated. When this loop exists and we know that the loop body will
be executed at least once if the loop header is entered. I'm not sure
of an easy means to determine this from the CFG.
> Converting whole loop optimizer at once can be huge task. Additionally some
> pieces should be already obsoletted (as code hoisting).
Yes, I agree. Before I got snowed under with my real job I had a big
hack at converting the old loop optimiser to use the CFG. The problem
was ensuring that I did not break anything. I think a better approach
is to write another loop optimiser that can run after the current loop
optimiser and then one day replace it completely. After the current
loop optimiser has run, I'd say that we should strip the loop notes
completely (or at least regenerate them from the CFG) and rely totally
on the CFG.
> So I think that in mid term, we can make the BIV/GIV discovery code idedendent
> on loop notes (it should not be that dificult) and start work on new loop
> unroller done as separate pass.
Yes, this could be done standalone. However, it would need loop
invariant discovery code first. This is one of the reasons I wrote
the dataflow code that Dan Berlin has marvellously souped up for the
new register allocator. It is also the reason I added routines to
loop.c for sinking and hoisting insns so that I could track the
changes to the dataflow.
What started to stump me was an efficient way to update the dataflow
information incrementally after each loop optimisation. To mitigate
the dataflow computation needed, I structured the loop optimiser to
optimise all the innermost loops first, then all the next level loops,
and so on. This way the loops could be optimised independently
without having to always recompute the dataflow information.
I've attached the basic structure of the data flow based loop
optimiser that I was working on.
Michael
-------------- next part --------------
static void
loop_notes_strip (f)
rtx f;
{
rtx insn;
/* Strip old loop notes. */
for (insn = f; insn; insn = NEXT_INSN (insn))
{
if (GET_CODE (insn) == NOTE
&& (NOTE_LINE_NUMBER (insn) == NOTE_INSN_LOOP_BEG
|| NOTE_LINE_NUMBER (insn) == NOTE_INSN_LOOP_CONT
|| NOTE_LINE_NUMBER (insn) == NOTE_INSN_LOOP_END))
delete_insn (insn);
}
}
static void
loop_note_stats_dump (f, stream)
rtx f;
FILE *stream;
{
struct {
int loops;
int vtops;
int conts;
} loop_stats;
/* Log old loop note stats. */
loop_stats.loops = 0;
loop_stats.conts = 0;
loop_stats.vtops = 0;
for (insn = f; insn; insn = NEXT_INSN (insn))
{
if (GET_CODE (insn) == NOTE
&& NOTE_LINE_NUMBER (insn) == NOTE_INSN_LOOP_BEG)
loop_stats.loops++;
else if (GET_CODE (insn) == NOTE
&& NOTE_LINE_NUMBER (insn) == NOTE_INSN_LOOP_CONT)
loop_stats.conts++;
else if (GET_CODE (insn) == NOTE
&& NOTE_LINE_NUMBER (insn) == NOTE_INSN_LOOP_VTOP)
loop_stats.vtops++;
}
if (stream)
fprintf (stream,
"\n;; Loop stats: %d loops, %d conts, %d vtops\n",
loop_stats.loops, loop_stats.conts, loop_stats.vtops);
}
/* Split an edge to create a new basic block and insert a
NOTE_INSN_DELETED as a placeholder. */
static void
loop_edge_split (e)
edge e;
{
rtx seq;
start_sequence ();
emit_note (NULL_PTR, NOTE_INSN_DELETED);
seq = gen_sequence ();
end_sequence ();
/* If we don't need to split the edge then we still want to add a
dummy note placeholder to the end of the block so that we can
insert insns before it without having to update bb->end all the
time. */
insert_insn_on_edge (seq, e);
}
static void
loop_pre_header_create (e)
edge e;
{
basic_block bb = e->src;
/* We know that this edge is the only forward edge into
the loop header. */
/* If the source of the edge has a single successor then the source
is the pre_header and we do not have to split any edges. */
if (bb->succ->succ_next == NULL
&& bb != ENTRY_BLOCK_PTR)
{
/* insert_insn_on_edge cannot handle a block with a single
successor and with a single jump_insn at the end. Note that
we know that the jump must be unconditional since the block
has a single successor. We can use this jump insn to hoist
insns before. */
if (GET_CODE (bb->end) == JUMP_INSN)
return;
/* We need a dummy insn at the end of the block to insert the
insn before. loop_edge_split will do this for us although no
actual edges get split. */
}
loop_edge_split (e);
}
static void
loop_landing_pad_create (e)
edge e;
{
loop_edge_split (e);
}
/* Set the invalid flag for each loop we cannot process. */
static void
loops_validate (loops)
struct loops *loops;
{
int i;
for (i = 0; i < loops->num; i++)
{
int j;
struct loop *loop = &loops->array[i];
/* Invalidate natural loops with multiple forward edges into the
loop header or with shared headers for now. These should be
transformed to have a single entry edge to simplify hoisting
or we could insert hoisted insns onto all entry edges.
Also invalidate loops with abnormal entry or exit edges
since we cannot split these edges. */
if (loop->num_entries != 1 || loop->shared
|| (loop->entry_edges[0]->flags & EDGE_ABNORMAL) != 0)
{
loop->invalid = 1;
continue;
}
for (j = 0; j < loop->num_exits; j++)
if ((loop->exit_edges[j]->flags & EDGE_ABNORMAL) != 0)
loop->invalid = 1;
}
}
/* Entry point of this file. Perform loop optimization
on the current function. F is the first insn of the function
and DUMPFILE is a stream for output of a trace of actions taken
(or 0 if none should be output). */
void
loop_optimize (f, dumpfile, flags)
/* f is the first instruction of a chain of insns for one function. */
rtx f;
FILE *dumpfile;
int flags;
{
register rtx insn;
register int i;
struct loops loops_data;
struct loops *loops = &loops_data;
struct loop_info *loops_info;
struct loop **loops_order;
int loop_level;
int n_basic_blocks_orig;
loop_dump_stream = dumpfile;
init_recog_no_volatile ();
max_reg_before_loop = max_reg_num ();
loop_note_stats_dump (f, loop_dump_stream);
loop_notes_strip (f);
/* Build CFG. */
find_basic_blocks (f, max_reg_before_loop, loop_dump_stream);
cleanup_cfg (f);
/* Find natural loops within the CFG. Only build the loop tree
and find the loop entry and exit edges. */
flow_loops_find (loops, LOOP_ALL);
if (! loops->num)
{
if (loop_dump_stream)
fprintf (loop_dump_stream,
";; Loop stats: 0 natural loops, %d marked loops\n",
loop_stats.loops);
flow_loops_free (loops);
return;
}
loops_validate (loops);
loops_bbs_find (loops);
/* Create pre-header and landing-pad nodes to hoist and sink insns
into. Use dummy note as a placeholder so that we can insert
insns before it. */
for (i = loops->num - 1; i >= 0; i--)
{
int j;
struct loop *loop = &loops->array[i];
if (loop->invalid)
continue;
loop_pre_header_create (loop->entry_edges[0]);
for (j = 0; j < loop->num_exits; j++)
if ((loop->exit_edges[j]->flags & EDGE_FAKE) == 0)
loop_landing_pad_create (loop->exit_edges[j]);
/* Try inverting the loop; i.e., converting a while loop
into a repeat loop. */
loop_invert (loop);
}
n_basic_blocks_orig = n_basic_blocks;
commit_edge_insertions ();
/* Add fake edges to the exit block for calls that may exit.
This will introduce new basic blocks and new loop exits.
We do not need landing pads for these new exits since
they will never get executed. */
loop_call_edges_add (loops);
if (n_basic_blocks != n_basic_blocks_orig)
{
if (loop_dump_stream)
fprintf (loop_dump_stream,
"\n;; Loop %d basic blocks created\n",
n_basic_blocks - n_basic_blocks_orig);
/* Update the loop data. This is only necessary if new basic
blocks were created. */
flow_loops_update (loops, LOOP_ALL);
loops_validate (loops);
}
/* Allocate and initialize auxiliary loop information. */
loops_info = xcalloc (loops->num, sizeof (struct loop_info));
for (i = 0; i < loops->num; i++)
loops->array[i].aux = loops_info + i;
/* Now find all register lifetimes. */
reg_scan (f, max_reg_before_loop, 1);
/* Initialise dataflow information. */
df = df_init ();
/* Copy pointer to dominator data. */
df->dom = loops->cfg.dom;
/* This must occur after reg_scan so that registers created by gcse
will have entries in the register tables.
We could have added a call to reg_scan after gcse_main in toplev.c,
but moving this call to init_alias_analysis is more efficient. */
init_alias_analysis ();
/* Link loops in terms of the loop level. */
loops_order = xcalloc (loops->levels + 1, sizeof (*loops_order));
for (i = loops->num - 1; i >= 0; i--)
{
struct loop *loop = &loops->array[i];
loop->next = loops_order[loop->level];
loops_order[loop->level] = loop;
}
/* Now optimize the loops with all the innermost loops first (level
1), followed by all the level 2 loops, etc. Note that at each
level the loops are independent and thus the transformations on a
loop should not screw up the dataflow information for other loops
at the same level. */
for (loop_level = 1; loop_level <= loops->levels; loop_level++)
{
struct loop *loop;
if (loop_dump_stream)
fprintf (loop_dump_stream,
"\n;; Processing loops at level %d.\n",
loop_level);
/* Create data flow information. */
if (df_analyse (df, 0, DF_LOOP))
df_dump (df, DF_LOOP_DUMP, loop_dump_stream);
/* Loop optimizations should only affect the dataflow
information for the blocks within the loop and for the loop
pre-header and landing-pad blocks. Newly created uses should
not be exposed above the loop pre-header unless an
optimization has gone wrong. Thus the existing bits within
the IN bitmaps for reaching uses should not change.
Similarly, newly created defs within the loop should not be
used after the end of the loop landing-pads. Thus there is
no need to propagate dataflow information before the loop
pre-header or after the loop landing-pads. */
/* Scan loops looking for valid ones to optimize. */
for (loop = loops_order[loop_level]; loop && !loop->invalid;
loop = loop->next)
{
struct loop_info *loop_info = LOOP_INFO (loop);
if (loop->invalid)
continue;
/* Set up variables describing this loop. */
loop_scan (loop);
/* Give up on this loop if it has a setjmp. */
if (loop_info->has_setjmp)
{
if (loop_dump_stream)
fprintf (loop_dump_stream,
"\n;; Loop%d ignored due to setjmp.\n",
loop->num);
loop->invalid = 1;
continue;
}
}
/* Perform loop invariant code motion. */
for (loop = loops_order[loop_level]; loop && !loop->invalid;
loop = loop->next)
{
struct loop_regs *regs = LOOP_REGS (loop);
if (loop_dump_stream)
fprintf (loop_dump_stream,
"\n;; Loop%d invariant code motion.\n",
loop->num);
loop_invariants_move (loop, flags);
}
/* Update data flow information. */
if (df_analyse (df, (sbitmap)-1, DF_LOOP))
df_dump (df, DF_LOOP_DUMP, loop_dump_stream);
/* Perform shadowing of loop mems. */
for (loop = loops_order[loop_level]; loop && !loop->invalid;
loop = loop->next)
{
if (loop_dump_stream)
fprintf (loop_dump_stream,
"\n;; Loop%d mem loading.\n",
loop->num);
loop_mems_load (loop);
}
/* Update data flow information for blocks that were
modified. */
if (df_analyse (df, (sbitmap)-1, DF_LOOP))
df_dump (df, DF_LOOP_DUMP, loop_dump_stream);
/* Find induction variables. */
for (loop = loops_order[loop_level]; loop && !loop->invalid;
loop = loop->next)
{
}
/* Invert loop. */
for (loop = loops_order[loop_level]; loop && !loop->invalid;
loop = loop->next)
{
}
/* Update data flow information for blocks that were
modified. */
if (df_analyse (df, (sbitmap)-1, DF_LOOP))
df_dump (df, DF_LOOP_DUMP, loop_dump_stream);
/* Unroll loop. */
for (loop = loops_order[loop_level]; loop && !loop->invalid;
loop = loop->next)
{
}
/* Update data flow information for blocks that were
modified. */
if (df_analyse (df, (sbitmap)-1, DF_LOOP))
df_dump (df, DF_LOOP_DUMP, loop_dump_stream);
/* Induction variable strength reduction. */
for (loop = loops_order[loop_level]; loop && !loop->invalid;
loop = loop->next)
{
}
}
/* If there were lexical blocks inside the loop, they have been
replicated. We will now have more than one NOTE_INSN_BLOCK_BEG
and NOTE_INSN_BLOCK_END for each such block. We must duplicate
the BLOCKs as well. */
if (write_symbols != NO_DEBUG)
reorder_blocks ();
end_alias_analysis ();
if (loop_dump_stream)
flow_loops_dump (loops, loop_dump_stream, loop_dump_aux, 1);
/* Clean up. */
df_finish (df);
free (loops_order);
free (loops_info);
flow_loops_free (loops);
}
More information about the Gcc
mailing list