mirror of
https://gcc.gnu.org/git/gcc.git
synced 2024-12-18 08:23:50 +08:00
ac91af71c9
This patch makes the entry point to loop invariant motion public, so that it can be called after loop interchange when that pass has swapped loops. This avoids the non-LTO -Ofast run-time regressions of 410.bwaves and 503.bwaves_r (which are 19% and 15% faster than current master on an AMD zen2 machine) while not introducing a full LIM pass into the pass pipeline. The patch also adds a parameter which allows not to perform any store motion so that it is not done after an interchange. gcc/ChangeLog: 2020-11-12 Martin Jambor <mjambor@suse.cz> PR tree-optimization/94406 * tree-ssa-loop-im.c (tree_ssa_lim): Renamed to loop_invariant_motion_in_fun, added a parameter to control store motion. (pass_lim::execute): Adjust call to tree_ssa_lim, now loop_invariant_motion_in_fun. * tree-ssa-loop-manip.h (loop_invariant_motion_in_fun): Declare. * gimple-loop-interchange.cc (pass_linterchange::execute): Call loop_invariant_motion_in_fun if any interchange has been done.
2104 lines
64 KiB
C++
2104 lines
64 KiB
C++
/* Loop interchange.
|
|
Copyright (C) 2017-2020 Free Software Foundation, Inc.
|
|
Contributed by ARM Ltd.
|
|
|
|
This file is part of GCC.
|
|
|
|
GCC is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by the
|
|
Free Software Foundation; either version 3, or (at your option) any
|
|
later version.
|
|
|
|
GCC is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GCC; see the file COPYING3. If not see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "backend.h"
|
|
#include "is-a.h"
|
|
#include "tree.h"
|
|
#include "gimple.h"
|
|
#include "tree-pass.h"
|
|
#include "ssa.h"
|
|
#include "gimple-pretty-print.h"
|
|
#include "fold-const.h"
|
|
#include "gimplify.h"
|
|
#include "gimple-iterator.h"
|
|
#include "gimplify-me.h"
|
|
#include "cfgloop.h"
|
|
#include "tree-ssa.h"
|
|
#include "tree-scalar-evolution.h"
|
|
#include "tree-ssa-loop-manip.h"
|
|
#include "tree-ssa-loop-niter.h"
|
|
#include "tree-ssa-loop-ivopts.h"
|
|
#include "tree-ssa-dce.h"
|
|
#include "tree-data-ref.h"
|
|
#include "tree-vectorizer.h"
|
|
|
|
/* This pass performs loop interchange: for example, the loop nest
|
|
|
|
for (int j = 0; j < N; j++)
|
|
for (int k = 0; k < N; k++)
|
|
for (int i = 0; i < N; i++)
|
|
c[i][j] = c[i][j] + a[i][k]*b[k][j];
|
|
|
|
is transformed to
|
|
|
|
for (int i = 0; i < N; i++)
|
|
for (int j = 0; j < N; j++)
|
|
for (int k = 0; k < N; k++)
|
|
c[i][j] = c[i][j] + a[i][k]*b[k][j];
|
|
|
|
This pass implements loop interchange in the following steps:
|
|
|
|
1) Find perfect loop nest for each innermost loop and compute data
|
|
dependence relations for it. For above example, loop nest is
|
|
<loop_j, loop_k, loop_i>.
|
|
2) From innermost to outermost loop, this pass tries to interchange
|
|
each loop pair. For above case, it firstly tries to interchange
|
|
<loop_k, loop_i> and loop nest becomes <loop_j, loop_i, loop_k>.
|
|
Then it tries to interchange <loop_j, loop_i> and loop nest becomes
|
|
<loop_i, loop_j, loop_k>. The overall effect is to move innermost
|
|
loop to the outermost position. For loop pair <loop_i, loop_j>
|
|
to be interchanged, we:
|
|
3) Check if data dependence relations are valid for loop interchange.
|
|
4) Check if both loops can be interchanged in terms of transformation.
|
|
5) Check if interchanging the two loops is profitable.
|
|
6) Interchange the two loops by mapping induction variables.
|
|
|
|
This pass also handles reductions in loop nest. So far we only support
|
|
simple reduction of inner loop and double reduction of the loop nest. */
|
|
|
|
/* Maximum number of stmts in each loop that should be interchanged. */
|
|
#define MAX_NUM_STMT (param_loop_interchange_max_num_stmts)
|
|
/* Maximum number of data references in loop nest. */
|
|
#define MAX_DATAREFS (param_loop_max_datarefs_for_datadeps)
|
|
|
|
/* Comparison ratio of access stride between inner/outer loops to be
|
|
interchanged. This is the minimum stride ratio for loop interchange
|
|
to be profitable. */
|
|
#define OUTER_STRIDE_RATIO (param_loop_interchange_stride_ratio)
|
|
/* The same as above, but we require higher ratio for interchanging the
|
|
innermost two loops. */
|
|
#define INNER_STRIDE_RATIO ((OUTER_STRIDE_RATIO) + 1)
|
|
|
|
/* Comparison ratio of stmt cost between inner/outer loops. Loops won't
|
|
be interchanged if outer loop has too many stmts. */
|
|
#define STMT_COST_RATIO (3)
|
|
|
|
/* Vector of strides that DR accesses in each level loop of a loop nest. */
|
|
#define DR_ACCESS_STRIDE(dr) ((vec<tree> *) dr->aux)
|
|
|
|
/* Structure recording loop induction variable. */
|
|
typedef struct induction
|
|
{
|
|
/* IV itself. */
|
|
tree var;
|
|
/* IV's initializing value, which is the init arg of the IV PHI node. */
|
|
tree init_val;
|
|
/* IV's initializing expr, which is (the expanded result of) init_val. */
|
|
tree init_expr;
|
|
/* IV's step. */
|
|
tree step;
|
|
} *induction_p;
|
|
|
|
/* Enum type for loop reduction variable. */
|
|
enum reduction_type
|
|
{
|
|
UNKNOWN_RTYPE = 0,
|
|
SIMPLE_RTYPE,
|
|
DOUBLE_RTYPE
|
|
};
|
|
|
|
/* Structure recording loop reduction variable. */
|
|
typedef struct reduction
|
|
{
|
|
/* Reduction itself. */
|
|
tree var;
|
|
/* PHI node defining reduction variable. */
|
|
gphi *phi;
|
|
/* Init and next variables of the reduction. */
|
|
tree init;
|
|
tree next;
|
|
/* Lcssa PHI node if reduction is used outside of its definition loop. */
|
|
gphi *lcssa_phi;
|
|
/* Stmts defining init and next. */
|
|
gimple *producer;
|
|
gimple *consumer;
|
|
/* If init is loaded from memory, this is the loading memory reference. */
|
|
tree init_ref;
|
|
/* If reduction is finally stored to memory, this is the stored memory
|
|
reference. */
|
|
tree fini_ref;
|
|
enum reduction_type type;
|
|
} *reduction_p;
|
|
|
|
|
|
/* Dump reduction RE. */
|
|
|
|
static void
|
|
dump_reduction (reduction_p re)
|
|
{
|
|
if (re->type == SIMPLE_RTYPE)
|
|
fprintf (dump_file, " Simple reduction: ");
|
|
else if (re->type == DOUBLE_RTYPE)
|
|
fprintf (dump_file, " Double reduction: ");
|
|
else
|
|
fprintf (dump_file, " Unknown reduction: ");
|
|
|
|
print_gimple_stmt (dump_file, re->phi, 0);
|
|
}
|
|
|
|
/* Dump LOOP's induction IV. */
|
|
static void
|
|
dump_induction (class loop *loop, induction_p iv)
|
|
{
|
|
fprintf (dump_file, " Induction: ");
|
|
print_generic_expr (dump_file, iv->var, TDF_SLIM);
|
|
fprintf (dump_file, " = {");
|
|
print_generic_expr (dump_file, iv->init_expr, TDF_SLIM);
|
|
fprintf (dump_file, ", ");
|
|
print_generic_expr (dump_file, iv->step, TDF_SLIM);
|
|
fprintf (dump_file, "}_%d\n", loop->num);
|
|
}
|
|
|
|
/* Loop candidate for interchange. */
|
|
|
|
class loop_cand
|
|
{
|
|
public:
|
|
loop_cand (class loop *, class loop *);
|
|
~loop_cand ();
|
|
|
|
reduction_p find_reduction_by_stmt (gimple *);
|
|
void classify_simple_reduction (reduction_p);
|
|
bool analyze_iloop_reduction_var (tree);
|
|
bool analyze_oloop_reduction_var (loop_cand *, tree);
|
|
bool analyze_induction_var (tree, tree);
|
|
bool analyze_carried_vars (loop_cand *);
|
|
bool analyze_lcssa_phis (void);
|
|
bool can_interchange_p (loop_cand *);
|
|
void undo_simple_reduction (reduction_p, bitmap);
|
|
|
|
/* The loop itself. */
|
|
class loop *m_loop;
|
|
/* The outer loop for interchange. It equals to loop if this loop cand
|
|
itself represents the outer loop. */
|
|
class loop *m_outer;
|
|
/* Vector of induction variables in loop. */
|
|
vec<induction_p> m_inductions;
|
|
/* Vector of reduction variables in loop. */
|
|
vec<reduction_p> m_reductions;
|
|
/* Lcssa PHI nodes of this loop. */
|
|
vec<gphi *> m_lcssa_nodes;
|
|
/* Single exit edge of this loop. */
|
|
edge m_exit;
|
|
/* Basic blocks of this loop. */
|
|
basic_block *m_bbs;
|
|
/* Number of stmts of this loop. Inner loops' stmts are not included. */
|
|
int m_num_stmts;
|
|
/* Number of constant initialized simple reduction. */
|
|
int m_const_init_reduc;
|
|
};
|
|
|
|
/* Constructor. */
|
|
|
|
loop_cand::loop_cand (class loop *loop, class loop *outer)
|
|
: m_loop (loop), m_outer (outer), m_exit (single_exit (loop)),
|
|
m_bbs (get_loop_body (loop)), m_num_stmts (0), m_const_init_reduc (0)
|
|
{
|
|
m_inductions.create (3);
|
|
m_reductions.create (3);
|
|
m_lcssa_nodes.create (3);
|
|
}
|
|
|
|
/* Destructor. */
|
|
|
|
loop_cand::~loop_cand ()
|
|
{
|
|
induction_p iv;
|
|
for (unsigned i = 0; m_inductions.iterate (i, &iv); ++i)
|
|
free (iv);
|
|
|
|
reduction_p re;
|
|
for (unsigned i = 0; m_reductions.iterate (i, &re); ++i)
|
|
free (re);
|
|
|
|
m_inductions.release ();
|
|
m_reductions.release ();
|
|
m_lcssa_nodes.release ();
|
|
free (m_bbs);
|
|
}
|
|
|
|
/* Return single use stmt of VAR in LOOP, otherwise return NULL. */
|
|
|
|
static gimple *
|
|
single_use_in_loop (tree var, class loop *loop)
|
|
{
|
|
gimple *stmt, *res = NULL;
|
|
use_operand_p use_p;
|
|
imm_use_iterator iterator;
|
|
|
|
FOR_EACH_IMM_USE_FAST (use_p, iterator, var)
|
|
{
|
|
stmt = USE_STMT (use_p);
|
|
if (is_gimple_debug (stmt))
|
|
continue;
|
|
|
|
if (!flow_bb_inside_loop_p (loop, gimple_bb (stmt)))
|
|
continue;
|
|
|
|
if (res)
|
|
return NULL;
|
|
|
|
res = stmt;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* Return true if E is unsupported in loop interchange, i.e, E is a complex
|
|
edge or part of irreducible loop. */
|
|
|
|
static inline bool
|
|
unsupported_edge (edge e)
|
|
{
|
|
return (e->flags & (EDGE_COMPLEX | EDGE_IRREDUCIBLE_LOOP));
|
|
}
|
|
|
|
/* Return the reduction if STMT is one of its lcssa PHI, producer or consumer
|
|
stmt. */
|
|
|
|
reduction_p
|
|
loop_cand::find_reduction_by_stmt (gimple *stmt)
|
|
{
|
|
gphi *phi = dyn_cast <gphi *> (stmt);
|
|
reduction_p re;
|
|
|
|
for (unsigned i = 0; m_reductions.iterate (i, &re); ++i)
|
|
if ((phi != NULL && phi == re->lcssa_phi)
|
|
|| (stmt == re->producer || stmt == re->consumer))
|
|
return re;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Return true if current loop_cand be interchanged. ILOOP is not NULL if
|
|
current loop_cand is outer loop in loop nest. */
|
|
|
|
bool
|
|
loop_cand::can_interchange_p (loop_cand *iloop)
|
|
{
|
|
/* For now we only support at most one reduction. */
|
|
unsigned allowed_reduction_num = 1;
|
|
|
|
/* Only support reduction if the loop nest to be interchanged is the
|
|
innermostin two loops. */
|
|
if ((iloop == NULL && m_loop->inner != NULL)
|
|
|| (iloop != NULL && iloop->m_loop->inner != NULL))
|
|
allowed_reduction_num = 0;
|
|
|
|
if (m_reductions.length () > allowed_reduction_num
|
|
|| (m_reductions.length () == 1
|
|
&& m_reductions[0]->type == UNKNOWN_RTYPE))
|
|
return false;
|
|
|
|
/* Only support lcssa PHI node which is for reduction. */
|
|
if (m_lcssa_nodes.length () > allowed_reduction_num)
|
|
return false;
|
|
|
|
/* Check if basic block has any unsupported operation. Note basic blocks
|
|
of inner loops are not checked here. */
|
|
for (unsigned i = 0; i < m_loop->num_nodes; i++)
|
|
{
|
|
basic_block bb = m_bbs[i];
|
|
gphi_iterator psi;
|
|
gimple_stmt_iterator gsi;
|
|
|
|
/* Skip basic blocks of inner loops. */
|
|
if (bb->loop_father != m_loop)
|
|
continue;
|
|
|
|
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
|
|
{
|
|
gimple *stmt = gsi_stmt (gsi);
|
|
if (is_gimple_debug (stmt))
|
|
continue;
|
|
|
|
if (gimple_has_side_effects (stmt))
|
|
return false;
|
|
|
|
m_num_stmts++;
|
|
if (gcall *call = dyn_cast <gcall *> (stmt))
|
|
{
|
|
/* In basic block of outer loop, the call should be cheap since
|
|
it will be moved to inner loop. */
|
|
if (iloop != NULL
|
|
&& !gimple_inexpensive_call_p (call))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
if (!iloop || !gimple_vuse (stmt))
|
|
continue;
|
|
|
|
/* Support stmt accessing memory in outer loop only if it is for
|
|
inner loop's reduction. */
|
|
if (iloop->find_reduction_by_stmt (stmt))
|
|
continue;
|
|
|
|
tree lhs;
|
|
/* Support loop invariant memory reference if it's only used once by
|
|
inner loop. */
|
|
/* ??? How's this checking for invariantness? */
|
|
if (gimple_assign_single_p (stmt)
|
|
&& (lhs = gimple_assign_lhs (stmt)) != NULL_TREE
|
|
&& TREE_CODE (lhs) == SSA_NAME
|
|
&& single_use_in_loop (lhs, iloop->m_loop))
|
|
continue;
|
|
|
|
return false;
|
|
}
|
|
/* Check if loop has too many stmts. */
|
|
if (m_num_stmts > MAX_NUM_STMT)
|
|
return false;
|
|
|
|
/* Allow PHI nodes in any basic block of inner loop, PHI nodes in outer
|
|
loop's header, or PHI nodes in dest bb of inner loop's exit edge. */
|
|
if (!iloop || bb == m_loop->header
|
|
|| bb == iloop->m_exit->dest)
|
|
continue;
|
|
|
|
/* Don't allow any other PHI nodes. */
|
|
for (psi = gsi_start_phis (bb); !gsi_end_p (psi); gsi_next (&psi))
|
|
if (!virtual_operand_p (PHI_RESULT (psi.phi ())))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Programmers and optimizers (like loop store motion) may optimize code:
|
|
|
|
for (int i = 0; i < N; i++)
|
|
for (int j = 0; j < N; j++)
|
|
a[i] += b[j][i] * c[j][i];
|
|
|
|
into reduction:
|
|
|
|
for (int i = 0; i < N; i++)
|
|
{
|
|
// producer. Note sum can be intitialized to a constant.
|
|
int sum = a[i];
|
|
for (int j = 0; j < N; j++)
|
|
{
|
|
sum += b[j][i] * c[j][i];
|
|
}
|
|
// consumer.
|
|
a[i] = sum;
|
|
}
|
|
|
|
The result code can't be interchanged without undoing the optimization.
|
|
This function classifies this kind reduction and records information so
|
|
that we can undo the store motion during interchange. */
|
|
|
|
void
|
|
loop_cand::classify_simple_reduction (reduction_p re)
|
|
{
|
|
gimple *producer, *consumer;
|
|
|
|
/* Check init variable of reduction and how it is initialized. */
|
|
if (TREE_CODE (re->init) == SSA_NAME)
|
|
{
|
|
producer = SSA_NAME_DEF_STMT (re->init);
|
|
re->producer = producer;
|
|
basic_block bb = gimple_bb (producer);
|
|
if (!bb || bb->loop_father != m_outer)
|
|
return;
|
|
|
|
if (!gimple_assign_load_p (producer))
|
|
return;
|
|
|
|
re->init_ref = gimple_assign_rhs1 (producer);
|
|
}
|
|
else if (CONSTANT_CLASS_P (re->init))
|
|
m_const_init_reduc++;
|
|
else
|
|
return;
|
|
|
|
/* Check how reduction variable is used. */
|
|
consumer = single_use_in_loop (PHI_RESULT (re->lcssa_phi), m_outer);
|
|
if (!consumer
|
|
|| !gimple_store_p (consumer))
|
|
return;
|
|
|
|
re->fini_ref = gimple_get_lhs (consumer);
|
|
re->consumer = consumer;
|
|
|
|
/* Simple reduction with constant initializer. */
|
|
if (!re->init_ref)
|
|
{
|
|
gcc_assert (CONSTANT_CLASS_P (re->init));
|
|
re->init_ref = unshare_expr (re->fini_ref);
|
|
}
|
|
|
|
/* Require memory references in producer and consumer are the same so
|
|
that we can undo reduction during interchange. */
|
|
if (re->init_ref && !operand_equal_p (re->init_ref, re->fini_ref, 0))
|
|
return;
|
|
|
|
re->type = SIMPLE_RTYPE;
|
|
}
|
|
|
|
/* Analyze reduction variable VAR for inner loop of the loop nest to be
|
|
interchanged. Return true if analysis succeeds. */
|
|
|
|
bool
|
|
loop_cand::analyze_iloop_reduction_var (tree var)
|
|
{
|
|
gphi *phi = as_a <gphi *> (SSA_NAME_DEF_STMT (var));
|
|
gphi *lcssa_phi = NULL, *use_phi;
|
|
tree init = PHI_ARG_DEF_FROM_EDGE (phi, loop_preheader_edge (m_loop));
|
|
tree next = PHI_ARG_DEF_FROM_EDGE (phi, loop_latch_edge (m_loop));
|
|
reduction_p re;
|
|
gimple *stmt, *next_def, *single_use = NULL;
|
|
use_operand_p use_p;
|
|
imm_use_iterator iterator;
|
|
|
|
if (TREE_CODE (next) != SSA_NAME)
|
|
return false;
|
|
|
|
next_def = SSA_NAME_DEF_STMT (next);
|
|
basic_block bb = gimple_bb (next_def);
|
|
if (!bb || !flow_bb_inside_loop_p (m_loop, bb))
|
|
return false;
|
|
|
|
/* In restricted reduction, the var is (and must be) used in defining
|
|
the updated var. The process can be depicted as below:
|
|
|
|
var ;; = PHI<init, next>
|
|
|
|
|
|
|
|
v
|
|
+---------------------+
|
|
| reduction operators | <-- other operands
|
|
+---------------------+
|
|
|
|
|
|
|
|
v
|
|
next
|
|
|
|
In terms loop interchange, we don't change how NEXT is computed based
|
|
on VAR and OTHER OPERANDS. In case of double reduction in loop nest
|
|
to be interchanged, we don't changed it at all. In the case of simple
|
|
reduction in inner loop, we only make change how VAR/NEXT is loaded or
|
|
stored. With these conditions, we can relax restrictions on reduction
|
|
in a way that reduction operation is seen as black box. In general,
|
|
we can ignore reassociation of reduction operator; we can handle fake
|
|
reductions in which VAR is not even used to compute NEXT. */
|
|
if (! single_imm_use (var, &use_p, &single_use)
|
|
|| ! flow_bb_inside_loop_p (m_loop, gimple_bb (single_use)))
|
|
return false;
|
|
|
|
/* Check the reduction operation. We require a left-associative operation.
|
|
For FP math we also need to be allowed to associate operations. */
|
|
if (gassign *ass = dyn_cast <gassign *> (single_use))
|
|
{
|
|
enum tree_code code = gimple_assign_rhs_code (ass);
|
|
if (! (associative_tree_code (code)
|
|
|| (code == MINUS_EXPR
|
|
&& use_p->use == gimple_assign_rhs1_ptr (ass)))
|
|
|| (FLOAT_TYPE_P (TREE_TYPE (var))
|
|
&& ! flag_associative_math))
|
|
return false;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
/* Handle and verify a series of stmts feeding the reduction op. */
|
|
if (single_use != next_def
|
|
&& !check_reduction_path (dump_user_location_t (), m_loop, phi, next,
|
|
gimple_assign_rhs_code (single_use)))
|
|
return false;
|
|
|
|
/* Only support cases in which INIT is used in inner loop. */
|
|
if (TREE_CODE (init) == SSA_NAME)
|
|
FOR_EACH_IMM_USE_FAST (use_p, iterator, init)
|
|
{
|
|
stmt = USE_STMT (use_p);
|
|
if (is_gimple_debug (stmt))
|
|
continue;
|
|
|
|
if (!flow_bb_inside_loop_p (m_loop, gimple_bb (stmt)))
|
|
return false;
|
|
}
|
|
|
|
FOR_EACH_IMM_USE_FAST (use_p, iterator, next)
|
|
{
|
|
stmt = USE_STMT (use_p);
|
|
if (is_gimple_debug (stmt))
|
|
continue;
|
|
|
|
/* Or else it's used in PHI itself. */
|
|
use_phi = dyn_cast <gphi *> (stmt);
|
|
if (use_phi == phi)
|
|
continue;
|
|
|
|
if (use_phi != NULL
|
|
&& lcssa_phi == NULL
|
|
&& gimple_bb (stmt) == m_exit->dest
|
|
&& PHI_ARG_DEF_FROM_EDGE (use_phi, m_exit) == next)
|
|
lcssa_phi = use_phi;
|
|
else
|
|
return false;
|
|
}
|
|
if (!lcssa_phi)
|
|
return false;
|
|
|
|
re = XCNEW (struct reduction);
|
|
re->var = var;
|
|
re->init = init;
|
|
re->next = next;
|
|
re->phi = phi;
|
|
re->lcssa_phi = lcssa_phi;
|
|
|
|
classify_simple_reduction (re);
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
dump_reduction (re);
|
|
|
|
m_reductions.safe_push (re);
|
|
return true;
|
|
}
|
|
|
|
/* Analyze reduction variable VAR for outer loop of the loop nest to be
|
|
interchanged. ILOOP is not NULL and points to inner loop. For the
|
|
moment, we only support double reduction for outer loop, like:
|
|
|
|
for (int i = 0; i < n; i++)
|
|
{
|
|
int sum = 0;
|
|
|
|
for (int j = 0; j < n; j++) // outer loop
|
|
for (int k = 0; k < n; k++) // inner loop
|
|
sum += a[i][k]*b[k][j];
|
|
|
|
s[i] = sum;
|
|
}
|
|
|
|
Note the innermost two loops are the loop nest to be interchanged.
|
|
Return true if analysis succeeds. */
|
|
|
|
bool
|
|
loop_cand::analyze_oloop_reduction_var (loop_cand *iloop, tree var)
|
|
{
|
|
gphi *phi = as_a <gphi *> (SSA_NAME_DEF_STMT (var));
|
|
gphi *lcssa_phi = NULL, *use_phi;
|
|
tree init = PHI_ARG_DEF_FROM_EDGE (phi, loop_preheader_edge (m_loop));
|
|
tree next = PHI_ARG_DEF_FROM_EDGE (phi, loop_latch_edge (m_loop));
|
|
reduction_p re;
|
|
gimple *stmt, *next_def;
|
|
use_operand_p use_p;
|
|
imm_use_iterator iterator;
|
|
|
|
if (TREE_CODE (next) != SSA_NAME)
|
|
return false;
|
|
|
|
next_def = SSA_NAME_DEF_STMT (next);
|
|
basic_block bb = gimple_bb (next_def);
|
|
if (!bb || !flow_bb_inside_loop_p (m_loop, bb))
|
|
return false;
|
|
|
|
/* Find inner loop's simple reduction that uses var as initializer. */
|
|
reduction_p inner_re = NULL;
|
|
for (unsigned i = 0; iloop->m_reductions.iterate (i, &inner_re); ++i)
|
|
if (inner_re->init == var || operand_equal_p (inner_re->init, var, 0))
|
|
break;
|
|
|
|
if (inner_re == NULL
|
|
|| inner_re->type != UNKNOWN_RTYPE
|
|
|| inner_re->producer != phi)
|
|
return false;
|
|
|
|
/* In case of double reduction, outer loop's reduction should be updated
|
|
by inner loop's simple reduction. */
|
|
if (next_def != inner_re->lcssa_phi)
|
|
return false;
|
|
|
|
/* Outer loop's reduction should only be used to initialize inner loop's
|
|
simple reduction. */
|
|
if (! single_imm_use (var, &use_p, &stmt)
|
|
|| stmt != inner_re->phi)
|
|
return false;
|
|
|
|
/* Check this reduction is correctly used outside of loop via lcssa phi. */
|
|
FOR_EACH_IMM_USE_FAST (use_p, iterator, next)
|
|
{
|
|
stmt = USE_STMT (use_p);
|
|
if (is_gimple_debug (stmt))
|
|
continue;
|
|
|
|
/* Or else it's used in PHI itself. */
|
|
use_phi = dyn_cast <gphi *> (stmt);
|
|
if (use_phi == phi)
|
|
continue;
|
|
|
|
if (lcssa_phi == NULL
|
|
&& use_phi != NULL
|
|
&& gimple_bb (stmt) == m_exit->dest
|
|
&& PHI_ARG_DEF_FROM_EDGE (use_phi, m_exit) == next)
|
|
lcssa_phi = use_phi;
|
|
else
|
|
return false;
|
|
}
|
|
if (!lcssa_phi)
|
|
return false;
|
|
|
|
re = XCNEW (struct reduction);
|
|
re->var = var;
|
|
re->init = init;
|
|
re->next = next;
|
|
re->phi = phi;
|
|
re->lcssa_phi = lcssa_phi;
|
|
re->type = DOUBLE_RTYPE;
|
|
inner_re->type = DOUBLE_RTYPE;
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
dump_reduction (re);
|
|
|
|
m_reductions.safe_push (re);
|
|
return true;
|
|
}
|
|
|
|
/* Return true if VAR is induction variable of current loop whose scev is
|
|
specified by CHREC. */
|
|
|
|
bool
|
|
loop_cand::analyze_induction_var (tree var, tree chrec)
|
|
{
|
|
gphi *phi = as_a <gphi *> (SSA_NAME_DEF_STMT (var));
|
|
tree init = PHI_ARG_DEF_FROM_EDGE (phi, loop_preheader_edge (m_loop));
|
|
|
|
/* Var is loop invariant, though it's unlikely to happen. */
|
|
if (tree_does_not_contain_chrecs (chrec))
|
|
{
|
|
/* Punt on floating point invariants if honoring signed zeros,
|
|
representing that as + 0.0 would change the result if init
|
|
is -0.0. Similarly for SNaNs it can raise exception. */
|
|
if (HONOR_SIGNED_ZEROS (chrec) || HONOR_SNANS (chrec))
|
|
return false;
|
|
struct induction *iv = XCNEW (struct induction);
|
|
iv->var = var;
|
|
iv->init_val = init;
|
|
iv->init_expr = chrec;
|
|
iv->step = build_zero_cst (TREE_TYPE (chrec));
|
|
m_inductions.safe_push (iv);
|
|
return true;
|
|
}
|
|
|
|
if (TREE_CODE (chrec) != POLYNOMIAL_CHREC
|
|
|| CHREC_VARIABLE (chrec) != (unsigned) m_loop->num
|
|
|| tree_contains_chrecs (CHREC_LEFT (chrec), NULL)
|
|
|| tree_contains_chrecs (CHREC_RIGHT (chrec), NULL))
|
|
return false;
|
|
|
|
struct induction *iv = XCNEW (struct induction);
|
|
iv->var = var;
|
|
iv->init_val = init;
|
|
iv->init_expr = CHREC_LEFT (chrec);
|
|
iv->step = CHREC_RIGHT (chrec);
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
dump_induction (m_loop, iv);
|
|
|
|
m_inductions.safe_push (iv);
|
|
return true;
|
|
}
|
|
|
|
/* Return true if all loop carried variables defined in loop header can
|
|
be successfully analyzed. */
|
|
|
|
bool
|
|
loop_cand::analyze_carried_vars (loop_cand *iloop)
|
|
{
|
|
edge e = loop_preheader_edge (m_outer);
|
|
gphi_iterator gsi;
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "\nLoop(%d) carried vars:\n", m_loop->num);
|
|
|
|
for (gsi = gsi_start_phis (m_loop->header); !gsi_end_p (gsi); gsi_next (&gsi))
|
|
{
|
|
gphi *phi = gsi.phi ();
|
|
|
|
tree var = PHI_RESULT (phi);
|
|
if (virtual_operand_p (var))
|
|
continue;
|
|
|
|
tree chrec = analyze_scalar_evolution (m_loop, var);
|
|
chrec = instantiate_scev (e, m_loop, chrec);
|
|
|
|
/* Analyze var as reduction variable. */
|
|
if (chrec_contains_undetermined (chrec)
|
|
|| chrec_contains_symbols_defined_in_loop (chrec, m_outer->num))
|
|
{
|
|
if (iloop && !analyze_oloop_reduction_var (iloop, var))
|
|
return false;
|
|
if (!iloop && !analyze_iloop_reduction_var (var))
|
|
return false;
|
|
}
|
|
/* Analyze var as induction variable. */
|
|
else if (!analyze_induction_var (var, chrec))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Return TRUE if loop closed PHI nodes can be analyzed successfully. */
|
|
|
|
bool
|
|
loop_cand::analyze_lcssa_phis (void)
|
|
{
|
|
gphi_iterator gsi;
|
|
for (gsi = gsi_start_phis (m_exit->dest); !gsi_end_p (gsi); gsi_next (&gsi))
|
|
{
|
|
gphi *phi = gsi.phi ();
|
|
|
|
if (virtual_operand_p (PHI_RESULT (phi)))
|
|
continue;
|
|
|
|
/* TODO: We only support lcssa phi for reduction for now. */
|
|
if (!find_reduction_by_stmt (phi))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* CONSUMER is a stmt in BB storing reduction result into memory object.
|
|
When the reduction is intialized from constant value, we need to add
|
|
a stmt loading from the memory object to target basic block in inner
|
|
loop during undoing the reduction. Problem is that memory reference
|
|
may use ssa variables not dominating the target basic block. This
|
|
function finds all stmts on which CONSUMER depends in basic block BB,
|
|
records and returns them via STMTS. */
|
|
|
|
static void
|
|
find_deps_in_bb_for_stmt (gimple_seq *stmts, basic_block bb, gimple *consumer)
|
|
{
|
|
auto_vec<gimple *, 4> worklist;
|
|
use_operand_p use_p;
|
|
ssa_op_iter iter;
|
|
gimple *stmt, *def_stmt;
|
|
gimple_stmt_iterator gsi;
|
|
|
|
/* First clear flag for stmts in bb. */
|
|
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
|
|
gimple_set_plf (gsi_stmt (gsi), GF_PLF_1, false);
|
|
|
|
/* DFS search all depended stmts in bb and mark flag for these stmts. */
|
|
worklist.safe_push (consumer);
|
|
while (!worklist.is_empty ())
|
|
{
|
|
stmt = worklist.pop ();
|
|
FOR_EACH_SSA_USE_OPERAND (use_p, stmt, iter, SSA_OP_USE)
|
|
{
|
|
def_stmt = SSA_NAME_DEF_STMT (USE_FROM_PTR (use_p));
|
|
|
|
if (is_a <gphi *> (def_stmt)
|
|
|| gimple_bb (def_stmt) != bb
|
|
|| gimple_plf (def_stmt, GF_PLF_1))
|
|
continue;
|
|
|
|
worklist.safe_push (def_stmt);
|
|
}
|
|
gimple_set_plf (stmt, GF_PLF_1, true);
|
|
}
|
|
for (gsi = gsi_start_nondebug_bb (bb);
|
|
!gsi_end_p (gsi) && (stmt = gsi_stmt (gsi)) != consumer;)
|
|
{
|
|
/* Move dep stmts to sequence STMTS. */
|
|
if (gimple_plf (stmt, GF_PLF_1))
|
|
{
|
|
gsi_remove (&gsi, false);
|
|
gimple_seq_add_stmt_without_update (stmts, stmt);
|
|
}
|
|
else
|
|
gsi_next_nondebug (&gsi);
|
|
}
|
|
}
|
|
|
|
/* User can write, optimizers can generate simple reduction RE for inner
|
|
loop. In order to make interchange valid, we have to undo reduction by
|
|
moving producer and consumer stmts into the inner loop. For example,
|
|
below code:
|
|
|
|
init = MEM_REF[idx]; //producer
|
|
loop:
|
|
var = phi<init, next>
|
|
next = var op ...
|
|
reduc_sum = phi<next>
|
|
MEM_REF[idx] = reduc_sum //consumer
|
|
|
|
is transformed into:
|
|
|
|
loop:
|
|
new_var = MEM_REF[idx]; //producer after moving
|
|
next = new_var op ...
|
|
MEM_REF[idx] = next; //consumer after moving
|
|
|
|
Note if the reduction variable is initialized to constant, like:
|
|
|
|
var = phi<0.0, next>
|
|
|
|
we compute new_var as below:
|
|
|
|
loop:
|
|
tmp = MEM_REF[idx];
|
|
new_var = !first_iteration ? tmp : 0.0;
|
|
|
|
so that the initial const is used in the first iteration of loop. Also
|
|
record ssa variables for dead code elimination in DCE_SEEDS. */
|
|
|
|
void
|
|
loop_cand::undo_simple_reduction (reduction_p re, bitmap dce_seeds)
|
|
{
|
|
gimple *stmt;
|
|
gimple_stmt_iterator from, to = gsi_after_labels (m_loop->header);
|
|
gimple_seq stmts = NULL;
|
|
tree new_var;
|
|
|
|
/* Prepare the initialization stmts and insert it to inner loop. */
|
|
if (re->producer != NULL)
|
|
{
|
|
gimple_set_vuse (re->producer, NULL_TREE);
|
|
update_stmt (re->producer);
|
|
from = gsi_for_stmt (re->producer);
|
|
gsi_remove (&from, false);
|
|
gimple_seq_add_stmt_without_update (&stmts, re->producer);
|
|
new_var = re->init;
|
|
}
|
|
else
|
|
{
|
|
/* Find all stmts on which expression "MEM_REF[idx]" depends. */
|
|
find_deps_in_bb_for_stmt (&stmts, gimple_bb (re->consumer), re->consumer);
|
|
/* Because we generate new stmt loading from the MEM_REF to TMP. */
|
|
tree cond, tmp = copy_ssa_name (re->var);
|
|
stmt = gimple_build_assign (tmp, re->init_ref);
|
|
gimple_seq_add_stmt_without_update (&stmts, stmt);
|
|
|
|
/* Init new_var to MEM_REF or CONST depending on if it is the first
|
|
iteration. */
|
|
induction_p iv = m_inductions[0];
|
|
cond = fold_build2 (NE_EXPR, boolean_type_node, iv->var, iv->init_val);
|
|
new_var = copy_ssa_name (re->var);
|
|
stmt = gimple_build_assign (new_var, COND_EXPR, cond, tmp, re->init);
|
|
gimple_seq_add_stmt_without_update (&stmts, stmt);
|
|
}
|
|
gsi_insert_seq_before (&to, stmts, GSI_SAME_STMT);
|
|
|
|
/* Replace all uses of reduction var with new variable. */
|
|
use_operand_p use_p;
|
|
imm_use_iterator iterator;
|
|
FOR_EACH_IMM_USE_STMT (stmt, iterator, re->var)
|
|
{
|
|
FOR_EACH_IMM_USE_ON_STMT (use_p, iterator)
|
|
SET_USE (use_p, new_var);
|
|
|
|
update_stmt (stmt);
|
|
}
|
|
|
|
/* Move consumer stmt into inner loop, just after reduction next's def. */
|
|
unlink_stmt_vdef (re->consumer);
|
|
release_ssa_name (gimple_vdef (re->consumer));
|
|
gimple_set_vdef (re->consumer, NULL_TREE);
|
|
gimple_set_vuse (re->consumer, NULL_TREE);
|
|
gimple_assign_set_rhs1 (re->consumer, re->next);
|
|
update_stmt (re->consumer);
|
|
from = gsi_for_stmt (re->consumer);
|
|
to = gsi_for_stmt (SSA_NAME_DEF_STMT (re->next));
|
|
gsi_move_after (&from, &to);
|
|
|
|
/* Mark the reduction variables for DCE. */
|
|
bitmap_set_bit (dce_seeds, SSA_NAME_VERSION (re->var));
|
|
bitmap_set_bit (dce_seeds, SSA_NAME_VERSION (PHI_RESULT (re->lcssa_phi)));
|
|
}
|
|
|
|
/* Free DATAREFS and its auxiliary memory. */
|
|
|
|
static void
|
|
free_data_refs_with_aux (vec<data_reference_p> datarefs)
|
|
{
|
|
data_reference_p dr;
|
|
for (unsigned i = 0; datarefs.iterate (i, &dr); ++i)
|
|
if (dr->aux != NULL)
|
|
{
|
|
DR_ACCESS_STRIDE (dr)->release ();
|
|
delete (vec<tree> *) dr->aux;
|
|
}
|
|
|
|
free_data_refs (datarefs);
|
|
}
|
|
|
|
/* Class for loop interchange transformation. */
|
|
|
|
class tree_loop_interchange
|
|
{
|
|
public:
|
|
tree_loop_interchange (vec<class loop *> loop_nest)
|
|
: m_loop_nest (loop_nest), m_niters_iv_var (NULL_TREE),
|
|
m_dce_seeds (BITMAP_ALLOC (NULL)) { }
|
|
~tree_loop_interchange () { BITMAP_FREE (m_dce_seeds); }
|
|
bool interchange (vec<data_reference_p>, vec<ddr_p>);
|
|
|
|
private:
|
|
void update_data_info (unsigned, unsigned, vec<data_reference_p>, vec<ddr_p>);
|
|
bool valid_data_dependences (unsigned, unsigned, vec<ddr_p>);
|
|
void interchange_loops (loop_cand &, loop_cand &);
|
|
void map_inductions_to_loop (loop_cand &, loop_cand &);
|
|
void move_code_to_inner_loop (class loop *, class loop *, basic_block *);
|
|
|
|
/* The whole loop nest in which interchange is ongoing. */
|
|
vec<class loop *> m_loop_nest;
|
|
/* We create new IV which is only used in loop's exit condition check.
|
|
In case of 3-level loop nest interchange, when we interchange the
|
|
innermost two loops, new IV created in the middle level loop does
|
|
not need to be preserved in interchanging the outermost two loops
|
|
later. We record the IV so that it can be skipped. */
|
|
tree m_niters_iv_var;
|
|
/* Bitmap of seed variables for dead code elimination after interchange. */
|
|
bitmap m_dce_seeds;
|
|
};
|
|
|
|
/* Update data refs' access stride and dependence information after loop
|
|
interchange. I_IDX/O_IDX gives indices of interchanged loops in loop
|
|
nest. DATAREFS are data references. DDRS are data dependences. */
|
|
|
|
void
|
|
tree_loop_interchange::update_data_info (unsigned i_idx, unsigned o_idx,
|
|
vec<data_reference_p> datarefs,
|
|
vec<ddr_p> ddrs)
|
|
{
|
|
struct data_reference *dr;
|
|
struct data_dependence_relation *ddr;
|
|
|
|
/* Update strides of data references. */
|
|
for (unsigned i = 0; datarefs.iterate (i, &dr); ++i)
|
|
{
|
|
vec<tree> *stride = DR_ACCESS_STRIDE (dr);
|
|
gcc_assert (stride->length () > i_idx);
|
|
std::swap ((*stride)[i_idx], (*stride)[o_idx]);
|
|
}
|
|
/* Update data dependences. */
|
|
for (unsigned i = 0; ddrs.iterate (i, &ddr); ++i)
|
|
if (DDR_ARE_DEPENDENT (ddr) != chrec_known)
|
|
{
|
|
for (unsigned j = 0; j < DDR_NUM_DIST_VECTS (ddr); ++j)
|
|
{
|
|
lambda_vector dist_vect = DDR_DIST_VECT (ddr, j);
|
|
std::swap (dist_vect[i_idx], dist_vect[o_idx]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check data dependence relations, return TRUE if it's valid to interchange
|
|
two loops specified by I_IDX/O_IDX. Theoretically, interchanging the two
|
|
loops is valid only if dist vector, after interchanging, doesn't have '>'
|
|
as the leftmost non-'=' direction. Practically, this function have been
|
|
conservative here by not checking some valid cases. */
|
|
|
|
bool
|
|
tree_loop_interchange::valid_data_dependences (unsigned i_idx, unsigned o_idx,
|
|
vec<ddr_p> ddrs)
|
|
{
|
|
struct data_dependence_relation *ddr;
|
|
|
|
for (unsigned i = 0; ddrs.iterate (i, &ddr); ++i)
|
|
{
|
|
/* Skip no-dependence case. */
|
|
if (DDR_ARE_DEPENDENT (ddr) == chrec_known)
|
|
continue;
|
|
|
|
for (unsigned j = 0; j < DDR_NUM_DIST_VECTS (ddr); ++j)
|
|
{
|
|
lambda_vector dist_vect = DDR_DIST_VECT (ddr, j);
|
|
unsigned level = dependence_level (dist_vect, m_loop_nest.length ());
|
|
|
|
/* If there is no carried dependence. */
|
|
if (level == 0)
|
|
continue;
|
|
|
|
level --;
|
|
|
|
/* If dependence is not carried by any loop in between the two
|
|
loops [oloop, iloop] to interchange. */
|
|
if (level < o_idx || level > i_idx)
|
|
continue;
|
|
|
|
/* Be conservative, skip case if either direction at i_idx/o_idx
|
|
levels is not '=' or '<'. */
|
|
if (dist_vect[i_idx] < 0 || dist_vect[o_idx] < 0)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Interchange two loops specified by ILOOP and OLOOP. */
|
|
|
|
void
|
|
tree_loop_interchange::interchange_loops (loop_cand &iloop, loop_cand &oloop)
|
|
{
|
|
reduction_p re;
|
|
gimple_stmt_iterator gsi;
|
|
tree i_niters, o_niters, var_after;
|
|
|
|
/* Undo inner loop's simple reduction. */
|
|
for (unsigned i = 0; iloop.m_reductions.iterate (i, &re); ++i)
|
|
if (re->type != DOUBLE_RTYPE)
|
|
{
|
|
if (re->producer)
|
|
reset_debug_uses (re->producer);
|
|
|
|
iloop.undo_simple_reduction (re, m_dce_seeds);
|
|
}
|
|
|
|
/* Only need to reset debug uses for double reduction. */
|
|
for (unsigned i = 0; oloop.m_reductions.iterate (i, &re); ++i)
|
|
{
|
|
gcc_assert (re->type == DOUBLE_RTYPE);
|
|
reset_debug_uses (SSA_NAME_DEF_STMT (re->var));
|
|
reset_debug_uses (SSA_NAME_DEF_STMT (re->next));
|
|
}
|
|
|
|
/* Prepare niters for both loops. */
|
|
class loop *loop_nest = m_loop_nest[0];
|
|
edge instantiate_below = loop_preheader_edge (loop_nest);
|
|
gsi = gsi_last_bb (loop_preheader_edge (loop_nest)->src);
|
|
i_niters = number_of_latch_executions (iloop.m_loop);
|
|
i_niters = analyze_scalar_evolution (loop_outer (iloop.m_loop), i_niters);
|
|
i_niters = instantiate_scev (instantiate_below, loop_outer (iloop.m_loop),
|
|
i_niters);
|
|
i_niters = force_gimple_operand_gsi (&gsi, unshare_expr (i_niters), true,
|
|
NULL_TREE, false, GSI_CONTINUE_LINKING);
|
|
o_niters = number_of_latch_executions (oloop.m_loop);
|
|
if (oloop.m_loop != loop_nest)
|
|
{
|
|
o_niters = analyze_scalar_evolution (loop_outer (oloop.m_loop), o_niters);
|
|
o_niters = instantiate_scev (instantiate_below, loop_outer (oloop.m_loop),
|
|
o_niters);
|
|
}
|
|
o_niters = force_gimple_operand_gsi (&gsi, unshare_expr (o_niters), true,
|
|
NULL_TREE, false, GSI_CONTINUE_LINKING);
|
|
|
|
/* Move src's code to tgt loop. This is necessary when src is the outer
|
|
loop and tgt is the inner loop. */
|
|
move_code_to_inner_loop (oloop.m_loop, iloop.m_loop, oloop.m_bbs);
|
|
|
|
/* Map outer loop's IV to inner loop, and vice versa. */
|
|
map_inductions_to_loop (oloop, iloop);
|
|
map_inductions_to_loop (iloop, oloop);
|
|
|
|
/* Create canonical IV for both loops. Note canonical IV for outer/inner
|
|
loop is actually from inner/outer loop. Also we record the new IV
|
|
created for the outer loop so that it can be skipped in later loop
|
|
interchange. */
|
|
create_canonical_iv (oloop.m_loop, oloop.m_exit,
|
|
i_niters, &m_niters_iv_var, &var_after);
|
|
bitmap_set_bit (m_dce_seeds, SSA_NAME_VERSION (var_after));
|
|
create_canonical_iv (iloop.m_loop, iloop.m_exit,
|
|
o_niters, NULL, &var_after);
|
|
bitmap_set_bit (m_dce_seeds, SSA_NAME_VERSION (var_after));
|
|
|
|
/* Scrap niters estimation of interchanged loops. */
|
|
iloop.m_loop->any_upper_bound = false;
|
|
iloop.m_loop->any_likely_upper_bound = false;
|
|
free_numbers_of_iterations_estimates (iloop.m_loop);
|
|
oloop.m_loop->any_upper_bound = false;
|
|
oloop.m_loop->any_likely_upper_bound = false;
|
|
free_numbers_of_iterations_estimates (oloop.m_loop);
|
|
|
|
/* Clear all cached scev information. This is expensive but shouldn't be
|
|
a problem given we interchange in very limited times. */
|
|
scev_reset_htab ();
|
|
|
|
/* ??? The association between the loop data structure and the
|
|
CFG changed, so what was loop N at the source level is now
|
|
loop M. We should think of retaining the association or breaking
|
|
it fully by creating a new loop instead of re-using the "wrong" one. */
|
|
}
|
|
|
|
/* Map induction variables of SRC loop to TGT loop. The function firstly
|
|
creates the same IV of SRC loop in TGT loop, then deletes the original
|
|
IV and re-initialize it using the newly created IV. For example, loop
|
|
nest:
|
|
|
|
for (i = 0; i < N; i++)
|
|
for (j = 0; j < M; j++)
|
|
{
|
|
//use of i;
|
|
//use of j;
|
|
}
|
|
|
|
will be transformed into:
|
|
|
|
for (jj = 0; jj < M; jj++)
|
|
for (ii = 0; ii < N; ii++)
|
|
{
|
|
//use of ii;
|
|
//use of jj;
|
|
}
|
|
|
|
after loop interchange. */
|
|
|
|
void
|
|
tree_loop_interchange::map_inductions_to_loop (loop_cand &src, loop_cand &tgt)
|
|
{
|
|
induction_p iv;
|
|
edge e = tgt.m_exit;
|
|
gimple_stmt_iterator incr_pos = gsi_last_bb (e->src), gsi;
|
|
|
|
/* Map source loop's IV to target loop. */
|
|
for (unsigned i = 0; src.m_inductions.iterate (i, &iv); ++i)
|
|
{
|
|
gimple *use_stmt, *stmt = SSA_NAME_DEF_STMT (iv->var);
|
|
gcc_assert (is_a <gphi *> (stmt));
|
|
|
|
use_operand_p use_p;
|
|
/* Only map original IV to target loop. */
|
|
if (m_niters_iv_var != iv->var)
|
|
{
|
|
/* Map the IV by creating the same one in target loop. */
|
|
tree var_before, var_after;
|
|
tree base = unshare_expr (iv->init_expr);
|
|
tree step = unshare_expr (iv->step);
|
|
create_iv (base, step, SSA_NAME_VAR (iv->var),
|
|
tgt.m_loop, &incr_pos, false, &var_before, &var_after);
|
|
bitmap_set_bit (m_dce_seeds, SSA_NAME_VERSION (var_before));
|
|
bitmap_set_bit (m_dce_seeds, SSA_NAME_VERSION (var_after));
|
|
|
|
/* Replace uses of the original IV var with newly created IV var. */
|
|
imm_use_iterator imm_iter;
|
|
FOR_EACH_IMM_USE_STMT (use_stmt, imm_iter, iv->var)
|
|
{
|
|
FOR_EACH_IMM_USE_ON_STMT (use_p, imm_iter)
|
|
SET_USE (use_p, var_before);
|
|
|
|
update_stmt (use_stmt);
|
|
}
|
|
}
|
|
|
|
/* Mark all uses for DCE. */
|
|
ssa_op_iter op_iter;
|
|
FOR_EACH_PHI_OR_STMT_USE (use_p, stmt, op_iter, SSA_OP_USE)
|
|
{
|
|
tree use = USE_FROM_PTR (use_p);
|
|
if (TREE_CODE (use) == SSA_NAME
|
|
&& ! SSA_NAME_IS_DEFAULT_DEF (use))
|
|
bitmap_set_bit (m_dce_seeds, SSA_NAME_VERSION (use));
|
|
}
|
|
|
|
/* Delete definition of the original IV in the source loop. */
|
|
gsi = gsi_for_stmt (stmt);
|
|
remove_phi_node (&gsi, true);
|
|
}
|
|
}
|
|
|
|
/* Move stmts of outer loop to inner loop. */
|
|
|
|
void
|
|
tree_loop_interchange::move_code_to_inner_loop (class loop *outer,
|
|
class loop *inner,
|
|
basic_block *outer_bbs)
|
|
{
|
|
basic_block oloop_exit_bb = single_exit (outer)->src;
|
|
gimple_stmt_iterator gsi, to;
|
|
|
|
for (unsigned i = 0; i < outer->num_nodes; i++)
|
|
{
|
|
basic_block bb = outer_bbs[i];
|
|
|
|
/* Skip basic blocks of inner loop. */
|
|
if (flow_bb_inside_loop_p (inner, bb))
|
|
continue;
|
|
|
|
/* Move code from header/latch to header/latch. */
|
|
if (bb == outer->header)
|
|
to = gsi_after_labels (inner->header);
|
|
else if (bb == outer->latch)
|
|
to = gsi_after_labels (inner->latch);
|
|
else
|
|
/* Otherwise, simply move to exit->src. */
|
|
to = gsi_last_bb (single_exit (inner)->src);
|
|
|
|
for (gsi = gsi_after_labels (bb); !gsi_end_p (gsi);)
|
|
{
|
|
gimple *stmt = gsi_stmt (gsi);
|
|
|
|
if (oloop_exit_bb == bb
|
|
&& stmt == gsi_stmt (gsi_last_bb (oloop_exit_bb)))
|
|
{
|
|
gsi_next (&gsi);
|
|
continue;
|
|
}
|
|
|
|
if (gimple_vdef (stmt))
|
|
{
|
|
unlink_stmt_vdef (stmt);
|
|
release_ssa_name (gimple_vdef (stmt));
|
|
gimple_set_vdef (stmt, NULL_TREE);
|
|
}
|
|
if (gimple_vuse (stmt))
|
|
{
|
|
gimple_set_vuse (stmt, NULL_TREE);
|
|
update_stmt (stmt);
|
|
}
|
|
|
|
reset_debug_uses (stmt);
|
|
gsi_move_before (&gsi, &to);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Given data reference DR in LOOP_NEST, the function computes DR's access
|
|
stride at each level of loop from innermost LOOP to outer. On success,
|
|
it saves access stride at each level loop in a vector which is pointed
|
|
by DR->aux. For example:
|
|
|
|
int arr[100][100][100];
|
|
for (i = 0; i < 100; i++) ;(DR->aux)strides[0] = 40000
|
|
for (j = 100; j > 0; j--) ;(DR->aux)strides[1] = 400
|
|
for (k = 0; k < 100; k++) ;(DR->aux)strides[2] = 4
|
|
arr[i][j - 1][k] = 0; */
|
|
|
|
static void
|
|
compute_access_stride (class loop *loop_nest, class loop *loop,
|
|
data_reference_p dr)
|
|
{
|
|
vec<tree> *strides = new vec<tree> ();
|
|
basic_block bb = gimple_bb (DR_STMT (dr));
|
|
|
|
while (!flow_bb_inside_loop_p (loop, bb))
|
|
{
|
|
strides->safe_push (build_int_cst (sizetype, 0));
|
|
loop = loop_outer (loop);
|
|
}
|
|
gcc_assert (loop == bb->loop_father);
|
|
|
|
tree ref = DR_REF (dr);
|
|
if (TREE_CODE (ref) == COMPONENT_REF
|
|
&& DECL_BIT_FIELD (TREE_OPERAND (ref, 1)))
|
|
{
|
|
/* We can't take address of bitfields. If the bitfield is at constant
|
|
offset from the start of the struct, just use address of the
|
|
struct, for analysis of the strides that shouldn't matter. */
|
|
if (!TREE_OPERAND (ref, 2)
|
|
|| TREE_CODE (TREE_OPERAND (ref, 2)) == INTEGER_CST)
|
|
ref = TREE_OPERAND (ref, 0);
|
|
/* Otherwise, if we have a bit field representative, use that. */
|
|
else if (DECL_BIT_FIELD_REPRESENTATIVE (TREE_OPERAND (ref, 1))
|
|
!= NULL_TREE)
|
|
{
|
|
tree repr = DECL_BIT_FIELD_REPRESENTATIVE (TREE_OPERAND (ref, 1));
|
|
ref = build3 (COMPONENT_REF, TREE_TYPE (repr), TREE_OPERAND (ref, 0),
|
|
repr, TREE_OPERAND (ref, 2));
|
|
}
|
|
/* Otherwise punt. */
|
|
else
|
|
{
|
|
dr->aux = strides;
|
|
return;
|
|
}
|
|
}
|
|
tree scev_base = build_fold_addr_expr (ref);
|
|
tree scev = analyze_scalar_evolution (loop, scev_base);
|
|
scev = instantiate_scev (loop_preheader_edge (loop_nest), loop, scev);
|
|
if (! chrec_contains_undetermined (scev))
|
|
{
|
|
tree sl = scev;
|
|
class loop *expected = loop;
|
|
while (TREE_CODE (sl) == POLYNOMIAL_CHREC)
|
|
{
|
|
class loop *sl_loop = get_chrec_loop (sl);
|
|
while (sl_loop != expected)
|
|
{
|
|
strides->safe_push (size_int (0));
|
|
expected = loop_outer (expected);
|
|
}
|
|
strides->safe_push (CHREC_RIGHT (sl));
|
|
sl = CHREC_LEFT (sl);
|
|
expected = loop_outer (expected);
|
|
}
|
|
if (! tree_contains_chrecs (sl, NULL))
|
|
while (expected != loop_outer (loop_nest))
|
|
{
|
|
strides->safe_push (size_int (0));
|
|
expected = loop_outer (expected);
|
|
}
|
|
}
|
|
|
|
dr->aux = strides;
|
|
}
|
|
|
|
/* Given loop nest LOOP_NEST with innermost LOOP, the function computes
|
|
access strides with respect to each level loop for all data refs in
|
|
DATAREFS from inner loop to outer loop. On success, it returns the
|
|
outermost loop that access strides can be computed successfully for
|
|
all data references. If access strides cannot be computed at least
|
|
for two levels of loop for any data reference, it returns NULL. */
|
|
|
|
static class loop *
|
|
compute_access_strides (class loop *loop_nest, class loop *loop,
|
|
vec<data_reference_p> datarefs)
|
|
{
|
|
unsigned i, j, num_loops = (unsigned) -1;
|
|
data_reference_p dr;
|
|
vec<tree> *stride;
|
|
|
|
for (i = 0; datarefs.iterate (i, &dr); ++i)
|
|
{
|
|
compute_access_stride (loop_nest, loop, dr);
|
|
stride = DR_ACCESS_STRIDE (dr);
|
|
if (stride->length () < num_loops)
|
|
{
|
|
num_loops = stride->length ();
|
|
if (num_loops < 2)
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
for (i = 0; datarefs.iterate (i, &dr); ++i)
|
|
{
|
|
stride = DR_ACCESS_STRIDE (dr);
|
|
if (stride->length () > num_loops)
|
|
stride->truncate (num_loops);
|
|
|
|
for (j = 0; j < (num_loops >> 1); ++j)
|
|
std::swap ((*stride)[j], (*stride)[num_loops - j - 1]);
|
|
}
|
|
|
|
loop = superloop_at_depth (loop, loop_depth (loop) + 1 - num_loops);
|
|
gcc_assert (loop_nest == loop || flow_loop_nested_p (loop_nest, loop));
|
|
return loop;
|
|
}
|
|
|
|
/* Prune access strides for data references in DATAREFS by removing strides
|
|
of loops that isn't in current LOOP_NEST. */
|
|
|
|
static void
|
|
prune_access_strides_not_in_loop (class loop *loop_nest,
|
|
class loop *innermost,
|
|
vec<data_reference_p> datarefs)
|
|
{
|
|
data_reference_p dr;
|
|
unsigned num_loops = loop_depth (innermost) - loop_depth (loop_nest) + 1;
|
|
gcc_assert (num_loops > 1);
|
|
|
|
/* Block remove strides of loops that is not in current loop nest. */
|
|
for (unsigned i = 0; datarefs.iterate (i, &dr); ++i)
|
|
{
|
|
vec<tree> *stride = DR_ACCESS_STRIDE (dr);
|
|
if (stride->length () > num_loops)
|
|
stride->block_remove (0, stride->length () - num_loops);
|
|
}
|
|
}
|
|
|
|
/* Dump access strides for all DATAREFS. */
|
|
|
|
static void
|
|
dump_access_strides (vec<data_reference_p> datarefs)
|
|
{
|
|
data_reference_p dr;
|
|
fprintf (dump_file, "Access Strides for DRs:\n");
|
|
for (unsigned i = 0; datarefs.iterate (i, &dr); ++i)
|
|
{
|
|
fprintf (dump_file, " ");
|
|
print_generic_expr (dump_file, DR_REF (dr), TDF_SLIM);
|
|
fprintf (dump_file, ":\t\t<");
|
|
|
|
vec<tree> *stride = DR_ACCESS_STRIDE (dr);
|
|
unsigned num_loops = stride->length ();
|
|
for (unsigned j = 0; j < num_loops; ++j)
|
|
{
|
|
print_generic_expr (dump_file, (*stride)[j], TDF_SLIM);
|
|
fprintf (dump_file, "%s", (j < num_loops - 1) ? ",\t" : ">\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Return true if it's profitable to interchange two loops whose index
|
|
in whole loop nest vector are I_IDX/O_IDX respectively. The function
|
|
computes and compares three types information from all DATAREFS:
|
|
1) Access stride for loop I_IDX and O_IDX.
|
|
2) Number of invariant memory references with respect to I_IDX before
|
|
and after loop interchange.
|
|
3) Flags indicating if all memory references access sequential memory
|
|
in ILOOP, before and after loop interchange.
|
|
If INNMOST_LOOP_P is true, the two loops for interchanging are the two
|
|
innermost loops in loop nest. This function also dumps information if
|
|
DUMP_INFO_P is true. */
|
|
|
|
static bool
|
|
should_interchange_loops (unsigned i_idx, unsigned o_idx,
|
|
vec<data_reference_p> datarefs,
|
|
unsigned i_stmt_cost, unsigned o_stmt_cost,
|
|
bool innermost_loops_p, bool dump_info_p = true)
|
|
{
|
|
unsigned HOST_WIDE_INT ratio;
|
|
unsigned i, j, num_old_inv_drs = 0, num_new_inv_drs = 0;
|
|
struct data_reference *dr;
|
|
bool all_seq_dr_before_p = true, all_seq_dr_after_p = true;
|
|
widest_int iloop_strides = 0, oloop_strides = 0;
|
|
unsigned num_unresolved_drs = 0;
|
|
unsigned num_resolved_ok_drs = 0;
|
|
unsigned num_resolved_not_ok_drs = 0;
|
|
|
|
if (dump_info_p && dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "\nData ref strides:\n\tmem_ref:\t\tiloop\toloop\n");
|
|
|
|
for (i = 0; datarefs.iterate (i, &dr); ++i)
|
|
{
|
|
vec<tree> *stride = DR_ACCESS_STRIDE (dr);
|
|
tree iloop_stride = (*stride)[i_idx], oloop_stride = (*stride)[o_idx];
|
|
|
|
bool subloop_stride_p = false;
|
|
/* Data ref can't be invariant or sequential access at current loop if
|
|
its address changes with respect to any subloops. */
|
|
for (j = i_idx + 1; j < stride->length (); ++j)
|
|
if (!integer_zerop ((*stride)[j]))
|
|
{
|
|
subloop_stride_p = true;
|
|
break;
|
|
}
|
|
|
|
if (integer_zerop (iloop_stride))
|
|
{
|
|
if (!subloop_stride_p)
|
|
num_old_inv_drs++;
|
|
}
|
|
if (integer_zerop (oloop_stride))
|
|
{
|
|
if (!subloop_stride_p)
|
|
num_new_inv_drs++;
|
|
}
|
|
|
|
if (TREE_CODE (iloop_stride) == INTEGER_CST
|
|
&& TREE_CODE (oloop_stride) == INTEGER_CST)
|
|
{
|
|
iloop_strides = wi::add (iloop_strides, wi::to_widest (iloop_stride));
|
|
oloop_strides = wi::add (oloop_strides, wi::to_widest (oloop_stride));
|
|
}
|
|
else if (multiple_of_p (TREE_TYPE (iloop_stride),
|
|
iloop_stride, oloop_stride))
|
|
num_resolved_ok_drs++;
|
|
else if (multiple_of_p (TREE_TYPE (iloop_stride),
|
|
oloop_stride, iloop_stride))
|
|
num_resolved_not_ok_drs++;
|
|
else
|
|
num_unresolved_drs++;
|
|
|
|
/* Data ref can't be sequential access if its address changes in sub
|
|
loop. */
|
|
if (subloop_stride_p)
|
|
{
|
|
all_seq_dr_before_p = false;
|
|
all_seq_dr_after_p = false;
|
|
continue;
|
|
}
|
|
/* Track if all data references are sequential accesses before/after loop
|
|
interchange. Note invariant is considered sequential here. */
|
|
tree access_size = TYPE_SIZE_UNIT (TREE_TYPE (DR_REF (dr)));
|
|
if (all_seq_dr_before_p
|
|
&& ! (integer_zerop (iloop_stride)
|
|
|| operand_equal_p (access_size, iloop_stride, 0)))
|
|
all_seq_dr_before_p = false;
|
|
if (all_seq_dr_after_p
|
|
&& ! (integer_zerop (oloop_stride)
|
|
|| operand_equal_p (access_size, oloop_stride, 0)))
|
|
all_seq_dr_after_p = false;
|
|
}
|
|
|
|
if (dump_info_p && dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "\toverall:\t\t");
|
|
print_decu (iloop_strides, dump_file);
|
|
fprintf (dump_file, "\t");
|
|
print_decu (oloop_strides, dump_file);
|
|
fprintf (dump_file, "\n");
|
|
|
|
fprintf (dump_file, "Invariant data ref: before(%d), after(%d)\n",
|
|
num_old_inv_drs, num_new_inv_drs);
|
|
fprintf (dump_file, "All consecutive stride: before(%s), after(%s)\n",
|
|
all_seq_dr_before_p ? "true" : "false",
|
|
all_seq_dr_after_p ? "true" : "false");
|
|
fprintf (dump_file, "OK to interchage with variable strides: %d\n",
|
|
num_resolved_ok_drs);
|
|
fprintf (dump_file, "Not OK to interchage with variable strides: %d\n",
|
|
num_resolved_not_ok_drs);
|
|
fprintf (dump_file, "Variable strides we cannot decide: %d\n",
|
|
num_unresolved_drs);
|
|
fprintf (dump_file, "Stmt cost of inner loop: %d\n", i_stmt_cost);
|
|
fprintf (dump_file, "Stmt cost of outer loop: %d\n", o_stmt_cost);
|
|
}
|
|
|
|
if (num_unresolved_drs != 0 || num_resolved_not_ok_drs != 0)
|
|
return false;
|
|
|
|
/* Stmts of outer loop will be moved to inner loop. If there are two many
|
|
such stmts, it could make inner loop costly. Here we compare stmt cost
|
|
between outer and inner loops. */
|
|
if (i_stmt_cost && o_stmt_cost
|
|
&& num_old_inv_drs + o_stmt_cost > num_new_inv_drs
|
|
&& o_stmt_cost * STMT_COST_RATIO > i_stmt_cost)
|
|
return false;
|
|
|
|
/* We use different stride comparison ratio for interchanging innermost
|
|
two loops or not. The idea is to be conservative in interchange for
|
|
the innermost loops. */
|
|
ratio = innermost_loops_p ? INNER_STRIDE_RATIO : OUTER_STRIDE_RATIO;
|
|
/* Do interchange if it gives better data locality behavior. */
|
|
if (wi::gtu_p (iloop_strides, wi::mul (oloop_strides, ratio)))
|
|
return true;
|
|
if (wi::gtu_p (iloop_strides, oloop_strides))
|
|
{
|
|
/* Or it creates more invariant memory references. */
|
|
if ((!all_seq_dr_before_p || all_seq_dr_after_p)
|
|
&& num_new_inv_drs > num_old_inv_drs)
|
|
return true;
|
|
/* Or it makes all memory references sequential. */
|
|
if (num_new_inv_drs >= num_old_inv_drs
|
|
&& !all_seq_dr_before_p && all_seq_dr_after_p)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Try to interchange inner loop of a loop nest to outer level. */
|
|
|
|
bool
|
|
tree_loop_interchange::interchange (vec<data_reference_p> datarefs,
|
|
vec<ddr_p> ddrs)
|
|
{
|
|
dump_user_location_t loc = find_loop_location (m_loop_nest[0]);
|
|
bool changed_p = false;
|
|
/* In each iteration we try to interchange I-th loop with (I+1)-th loop.
|
|
The overall effect is to push inner loop to outermost level in whole
|
|
loop nest. */
|
|
for (unsigned i = m_loop_nest.length (); i > 1; --i)
|
|
{
|
|
unsigned i_idx = i - 1, o_idx = i - 2;
|
|
|
|
/* Check validity for loop interchange. */
|
|
if (!valid_data_dependences (i_idx, o_idx, ddrs))
|
|
break;
|
|
|
|
loop_cand iloop (m_loop_nest[i_idx], m_loop_nest[o_idx]);
|
|
loop_cand oloop (m_loop_nest[o_idx], m_loop_nest[o_idx]);
|
|
|
|
/* Check if we can do transformation for loop interchange. */
|
|
if (!iloop.analyze_carried_vars (NULL)
|
|
|| !iloop.analyze_lcssa_phis ()
|
|
|| !oloop.analyze_carried_vars (&iloop)
|
|
|| !oloop.analyze_lcssa_phis ()
|
|
|| !iloop.can_interchange_p (NULL)
|
|
|| !oloop.can_interchange_p (&iloop))
|
|
break;
|
|
|
|
/* Outer loop's stmts will be moved to inner loop during interchange.
|
|
If there are many of them, it may make inner loop very costly. We
|
|
need to check number of outer loop's stmts in profit cost model of
|
|
interchange. */
|
|
int stmt_cost = oloop.m_num_stmts;
|
|
/* Count out the exit checking stmt of outer loop. */
|
|
stmt_cost --;
|
|
/* Count out IV's increasing stmt, IVOPTs takes care if it. */
|
|
stmt_cost -= oloop.m_inductions.length ();
|
|
/* Count in the additional load and cond_expr stmts caused by inner
|
|
loop's constant initialized reduction. */
|
|
stmt_cost += iloop.m_const_init_reduc * 2;
|
|
if (stmt_cost < 0)
|
|
stmt_cost = 0;
|
|
|
|
/* Check profitability for loop interchange. */
|
|
if (should_interchange_loops (i_idx, o_idx, datarefs,
|
|
(unsigned) iloop.m_num_stmts,
|
|
(unsigned) stmt_cost,
|
|
iloop.m_loop->inner == NULL))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file,
|
|
"Loop_pair<outer:%d, inner:%d> is interchanged\n\n",
|
|
oloop.m_loop->num, iloop.m_loop->num);
|
|
|
|
changed_p = true;
|
|
interchange_loops (iloop, oloop);
|
|
/* No need to update if there is no further loop interchange. */
|
|
if (o_idx > 0)
|
|
update_data_info (i_idx, o_idx, datarefs, ddrs);
|
|
}
|
|
else
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file,
|
|
"Loop_pair<outer:%d, inner:%d> is not interchanged\n\n",
|
|
oloop.m_loop->num, iloop.m_loop->num);
|
|
}
|
|
}
|
|
simple_dce_from_worklist (m_dce_seeds);
|
|
|
|
if (changed_p && dump_enabled_p ())
|
|
dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc,
|
|
"loops interchanged in loop nest\n");
|
|
|
|
return changed_p;
|
|
}
|
|
|
|
|
|
/* Loop interchange pass. */
|
|
|
|
namespace {
|
|
|
|
const pass_data pass_data_linterchange =
|
|
{
|
|
GIMPLE_PASS, /* type */
|
|
"linterchange", /* name */
|
|
OPTGROUP_LOOP, /* optinfo_flags */
|
|
TV_LINTERCHANGE, /* tv_id */
|
|
PROP_cfg, /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
0, /* todo_flags_finish */
|
|
};
|
|
|
|
class pass_linterchange : public gimple_opt_pass
|
|
{
|
|
public:
|
|
pass_linterchange (gcc::context *ctxt)
|
|
: gimple_opt_pass (pass_data_linterchange, ctxt)
|
|
{}
|
|
|
|
/* opt_pass methods: */
|
|
opt_pass * clone () { return new pass_linterchange (m_ctxt); }
|
|
virtual bool gate (function *) { return flag_loop_interchange; }
|
|
virtual unsigned int execute (function *);
|
|
|
|
}; // class pass_linterchange
|
|
|
|
|
|
/* Return true if LOOP has proper form for interchange. We check three
|
|
conditions in the function:
|
|
1) In general, a loop can be interchanged only if it doesn't have
|
|
basic blocks other than header, exit and latch besides possible
|
|
inner loop nest. This basically restricts loop interchange to
|
|
below form loop nests:
|
|
|
|
header<---+
|
|
| |
|
|
v |
|
|
INNER_LOOP |
|
|
| |
|
|
v |
|
|
exit--->latch
|
|
|
|
2) Data reference in basic block that executes in different times
|
|
than loop head/exit is not allowed.
|
|
3) Record the innermost outer loop that doesn't form rectangle loop
|
|
nest with LOOP. */
|
|
|
|
static bool
|
|
proper_loop_form_for_interchange (class loop *loop, class loop **min_outer)
|
|
{
|
|
edge e0, e1, exit;
|
|
|
|
/* Don't interchange if loop has unsupported information for the moment. */
|
|
if (loop->safelen > 0
|
|
|| loop->constraints != 0
|
|
|| loop->can_be_parallel
|
|
|| loop->dont_vectorize
|
|
|| loop->force_vectorize
|
|
|| loop->in_oacc_kernels_region
|
|
|| loop->orig_loop_num != 0
|
|
|| loop->simduid != NULL_TREE)
|
|
return false;
|
|
|
|
/* Don't interchange if outer loop has basic block other than header, exit
|
|
and latch. */
|
|
if (loop->inner != NULL
|
|
&& loop->num_nodes != loop->inner->num_nodes + 3)
|
|
return false;
|
|
|
|
if ((exit = single_dom_exit (loop)) == NULL)
|
|
return false;
|
|
|
|
/* Check control flow on loop header/exit blocks. */
|
|
if (loop->header == exit->src
|
|
&& (EDGE_COUNT (loop->header->preds) != 2
|
|
|| EDGE_COUNT (loop->header->succs) != 2))
|
|
return false;
|
|
else if (loop->header != exit->src
|
|
&& (EDGE_COUNT (loop->header->preds) != 2
|
|
|| !single_succ_p (loop->header)
|
|
|| unsupported_edge (single_succ_edge (loop->header))
|
|
|| EDGE_COUNT (exit->src->succs) != 2
|
|
|| !single_pred_p (exit->src)
|
|
|| unsupported_edge (single_pred_edge (exit->src))))
|
|
return false;
|
|
|
|
e0 = EDGE_PRED (loop->header, 0);
|
|
e1 = EDGE_PRED (loop->header, 1);
|
|
if (unsupported_edge (e0) || unsupported_edge (e1)
|
|
|| (e0->src != loop->latch && e1->src != loop->latch)
|
|
|| (e0->src->loop_father == loop && e1->src->loop_father == loop))
|
|
return false;
|
|
|
|
e0 = EDGE_SUCC (exit->src, 0);
|
|
e1 = EDGE_SUCC (exit->src, 1);
|
|
if (unsupported_edge (e0) || unsupported_edge (e1)
|
|
|| (e0->dest != loop->latch && e1->dest != loop->latch)
|
|
|| (e0->dest->loop_father == loop && e1->dest->loop_father == loop))
|
|
return false;
|
|
|
|
/* Don't interchange if any reference is in basic block that doesn't
|
|
dominate exit block. */
|
|
basic_block *bbs = get_loop_body (loop);
|
|
for (unsigned i = 0; i < loop->num_nodes; i++)
|
|
{
|
|
basic_block bb = bbs[i];
|
|
|
|
if (bb->loop_father != loop
|
|
|| bb == loop->header || bb == exit->src
|
|
|| dominated_by_p (CDI_DOMINATORS, exit->src, bb))
|
|
continue;
|
|
|
|
for (gimple_stmt_iterator gsi = gsi_start_nondebug_bb (bb);
|
|
!gsi_end_p (gsi); gsi_next_nondebug (&gsi))
|
|
if (gimple_vuse (gsi_stmt (gsi)))
|
|
{
|
|
free (bbs);
|
|
return false;
|
|
}
|
|
}
|
|
free (bbs);
|
|
|
|
tree niters = number_of_latch_executions (loop);
|
|
niters = analyze_scalar_evolution (loop_outer (loop), niters);
|
|
if (!niters || chrec_contains_undetermined (niters))
|
|
return false;
|
|
|
|
/* Record the innermost outer loop that doesn't form rectangle loop nest. */
|
|
for (loop_p loop2 = loop_outer (loop);
|
|
loop2 && flow_loop_nested_p (*min_outer, loop2);
|
|
loop2 = loop_outer (loop2))
|
|
{
|
|
niters = instantiate_scev (loop_preheader_edge (loop2),
|
|
loop_outer (loop), niters);
|
|
if (!evolution_function_is_invariant_p (niters, loop2->num))
|
|
{
|
|
*min_outer = loop2;
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Return true if any two adjacent loops in loop nest [INNERMOST, LOOP_NEST]
|
|
should be interchanged by looking into all DATAREFS. */
|
|
|
|
static bool
|
|
should_interchange_loop_nest (class loop *loop_nest, class loop *innermost,
|
|
vec<data_reference_p> datarefs)
|
|
{
|
|
unsigned idx = loop_depth (innermost) - loop_depth (loop_nest);
|
|
gcc_assert (idx > 0);
|
|
|
|
/* Check if any two adjacent loops should be interchanged. */
|
|
for (class loop *loop = innermost;
|
|
loop != loop_nest; loop = loop_outer (loop), idx--)
|
|
if (should_interchange_loops (idx, idx - 1, datarefs, 0, 0,
|
|
loop == innermost, false))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Given loop nest LOOP_NEST and data references DATAREFS, compute data
|
|
dependences for loop interchange and store it in DDRS. Note we compute
|
|
dependences directly rather than call generic interface so that we can
|
|
return on unknown dependence instantly. */
|
|
|
|
static bool
|
|
tree_loop_interchange_compute_ddrs (vec<loop_p> loop_nest,
|
|
vec<data_reference_p> datarefs,
|
|
vec<ddr_p> *ddrs)
|
|
{
|
|
struct data_reference *a, *b;
|
|
class loop *innermost = loop_nest.last ();
|
|
|
|
for (unsigned i = 0; datarefs.iterate (i, &a); ++i)
|
|
{
|
|
bool a_outer_p = gimple_bb (DR_STMT (a))->loop_father != innermost;
|
|
for (unsigned j = i + 1; datarefs.iterate (j, &b); ++j)
|
|
if (DR_IS_WRITE (a) || DR_IS_WRITE (b))
|
|
{
|
|
bool b_outer_p = gimple_bb (DR_STMT (b))->loop_father != innermost;
|
|
/* Don't support multiple write references in outer loop. */
|
|
if (a_outer_p && b_outer_p && DR_IS_WRITE (a) && DR_IS_WRITE (b))
|
|
return false;
|
|
|
|
ddr_p ddr = initialize_data_dependence_relation (a, b, loop_nest);
|
|
ddrs->safe_push (ddr);
|
|
compute_affine_dependence (ddr, loop_nest[0]);
|
|
|
|
/* Give up if ddr is unknown dependence or classic direct vector
|
|
is not available. */
|
|
if (DDR_ARE_DEPENDENT (ddr) == chrec_dont_know
|
|
|| (DDR_ARE_DEPENDENT (ddr) == NULL_TREE
|
|
&& DDR_NUM_DIR_VECTS (ddr) == 0))
|
|
return false;
|
|
|
|
/* If either data references is in outer loop of nest, we require
|
|
no dependence here because the data reference need to be moved
|
|
into inner loop during interchange. */
|
|
if (a_outer_p && b_outer_p
|
|
&& operand_equal_p (DR_REF (a), DR_REF (b), 0))
|
|
continue;
|
|
if (DDR_ARE_DEPENDENT (ddr) != chrec_known
|
|
&& (a_outer_p || b_outer_p))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Prune DATAREFS by removing any data reference not inside of LOOP. */
|
|
|
|
static inline void
|
|
prune_datarefs_not_in_loop (class loop *loop, vec<data_reference_p> datarefs)
|
|
{
|
|
unsigned i, j;
|
|
struct data_reference *dr;
|
|
|
|
for (i = 0, j = 0; datarefs.iterate (i, &dr); ++i)
|
|
{
|
|
if (flow_bb_inside_loop_p (loop, gimple_bb (DR_STMT (dr))))
|
|
datarefs[j++] = dr;
|
|
else
|
|
{
|
|
if (dr->aux)
|
|
{
|
|
DR_ACCESS_STRIDE (dr)->release ();
|
|
delete (vec<tree> *) dr->aux;
|
|
}
|
|
free_data_ref (dr);
|
|
}
|
|
}
|
|
datarefs.truncate (j);
|
|
}
|
|
|
|
/* Find and store data references in DATAREFS for LOOP nest. If there's
|
|
difficult data reference in a basic block, we shrink the loop nest to
|
|
inner loop of that basic block's father loop. On success, return the
|
|
outer loop of the result loop nest. */
|
|
|
|
static class loop *
|
|
prepare_data_references (class loop *loop, vec<data_reference_p> *datarefs)
|
|
{
|
|
class loop *loop_nest = loop;
|
|
vec<data_reference_p> *bb_refs;
|
|
basic_block bb, *bbs = get_loop_body_in_dom_order (loop);
|
|
|
|
for (unsigned i = 0; i < loop->num_nodes; i++)
|
|
bbs[i]->aux = NULL;
|
|
|
|
/* Find data references for all basic blocks. Shrink loop nest on difficult
|
|
data reference. */
|
|
for (unsigned i = 0; loop_nest && i < loop->num_nodes; ++i)
|
|
{
|
|
bb = bbs[i];
|
|
if (!flow_bb_inside_loop_p (loop_nest, bb))
|
|
continue;
|
|
|
|
bb_refs = new vec<data_reference_p> ();
|
|
if (find_data_references_in_bb (loop, bb, bb_refs) == chrec_dont_know)
|
|
{
|
|
loop_nest = bb->loop_father->inner;
|
|
if (loop_nest && !loop_nest->inner)
|
|
loop_nest = NULL;
|
|
|
|
free_data_refs (*bb_refs);
|
|
delete bb_refs;
|
|
}
|
|
else if (bb_refs->is_empty ())
|
|
delete bb_refs;
|
|
else
|
|
bb->aux = bb_refs;
|
|
}
|
|
|
|
/* Collect all data references in loop nest. */
|
|
for (unsigned i = 0; i < loop->num_nodes; i++)
|
|
{
|
|
bb = bbs[i];
|
|
if (!bb->aux)
|
|
continue;
|
|
|
|
bb_refs = (vec<data_reference_p> *) bb->aux;
|
|
if (loop_nest && flow_bb_inside_loop_p (loop_nest, bb))
|
|
datarefs->safe_splice (*bb_refs);
|
|
else
|
|
free_data_refs (*bb_refs);
|
|
|
|
delete bb_refs;
|
|
bb->aux = NULL;
|
|
}
|
|
free (bbs);
|
|
|
|
return loop_nest;
|
|
}
|
|
|
|
/* Given innermost LOOP, return true if perfect loop nest can be found and
|
|
data dependences can be computed. If succeed, record the perfect loop
|
|
nest in LOOP_NEST; record all data references in DATAREFS and record all
|
|
data dependence relations in DDRS.
|
|
|
|
We do support a restricted form of imperfect loop nest, i.e, loop nest
|
|
with load/store in outer loop initializing/finalizing simple reduction
|
|
of the innermost loop. For such outer loop reference, we require that
|
|
it has no dependence with others sinve it will be moved to inner loop
|
|
in interchange. */
|
|
|
|
static bool
|
|
prepare_perfect_loop_nest (class loop *loop, vec<loop_p> *loop_nest,
|
|
vec<data_reference_p> *datarefs, vec<ddr_p> *ddrs)
|
|
{
|
|
class loop *start_loop = NULL, *innermost = loop;
|
|
class loop *outermost = loops_for_fn (cfun)->tree_root;
|
|
|
|
/* Find loop nest from the innermost loop. The outermost is the innermost
|
|
outer*/
|
|
while (loop->num != 0 && loop->inner == start_loop
|
|
&& flow_loop_nested_p (outermost, loop))
|
|
{
|
|
if (!proper_loop_form_for_interchange (loop, &outermost))
|
|
break;
|
|
|
|
start_loop = loop;
|
|
/* If this loop has sibling loop, the father loop won't be in perfect
|
|
loop nest. */
|
|
if (loop->next != NULL)
|
|
break;
|
|
|
|
loop = loop_outer (loop);
|
|
}
|
|
if (!start_loop || !start_loop->inner)
|
|
return false;
|
|
|
|
/* Prepare the data reference vector for the loop nest, pruning outer
|
|
loops we cannot handle. */
|
|
start_loop = prepare_data_references (start_loop, datarefs);
|
|
if (!start_loop
|
|
/* Check if there is no data reference. */
|
|
|| datarefs->is_empty ()
|
|
/* Check if there are too many of data references. */
|
|
|| (int) datarefs->length () > MAX_DATAREFS)
|
|
return false;
|
|
|
|
/* Compute access strides for all data references, pruning outer
|
|
loops we cannot analyze refs in. */
|
|
start_loop = compute_access_strides (start_loop, innermost, *datarefs);
|
|
if (!start_loop)
|
|
return false;
|
|
|
|
/* Check if any interchange is profitable in the loop nest. */
|
|
if (!should_interchange_loop_nest (start_loop, innermost, *datarefs))
|
|
return false;
|
|
|
|
/* Check if data dependences can be computed for loop nest starting from
|
|
start_loop. */
|
|
loop = start_loop;
|
|
do {
|
|
loop_nest->truncate (0);
|
|
|
|
if (loop != start_loop)
|
|
prune_datarefs_not_in_loop (start_loop, *datarefs);
|
|
|
|
if (find_loop_nest (start_loop, loop_nest)
|
|
&& tree_loop_interchange_compute_ddrs (*loop_nest, *datarefs, ddrs))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file,
|
|
"\nConsider loop interchange for loop_nest<%d - %d>\n",
|
|
start_loop->num, innermost->num);
|
|
|
|
if (loop != start_loop)
|
|
prune_access_strides_not_in_loop (start_loop, innermost, *datarefs);
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
dump_access_strides (*datarefs);
|
|
|
|
return true;
|
|
}
|
|
|
|
free_dependence_relations (*ddrs);
|
|
*ddrs = vNULL;
|
|
/* Try to compute data dependences with the outermost loop stripped. */
|
|
loop = start_loop;
|
|
start_loop = start_loop->inner;
|
|
} while (start_loop && start_loop->inner);
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Main entry for loop interchange pass. */
|
|
|
|
unsigned int
|
|
pass_linterchange::execute (function *fun)
|
|
{
|
|
if (number_of_loops (fun) <= 2)
|
|
return 0;
|
|
|
|
bool changed_p = false;
|
|
class loop *loop;
|
|
FOR_EACH_LOOP (loop, LI_ONLY_INNERMOST)
|
|
{
|
|
vec<loop_p> loop_nest = vNULL;
|
|
vec<data_reference_p> datarefs = vNULL;
|
|
vec<ddr_p> ddrs = vNULL;
|
|
if (prepare_perfect_loop_nest (loop, &loop_nest, &datarefs, &ddrs))
|
|
{
|
|
tree_loop_interchange loop_interchange (loop_nest);
|
|
changed_p |= loop_interchange.interchange (datarefs, ddrs);
|
|
}
|
|
free_dependence_relations (ddrs);
|
|
free_data_refs_with_aux (datarefs);
|
|
loop_nest.release ();
|
|
}
|
|
|
|
if (changed_p)
|
|
{
|
|
unsigned todo = TODO_update_ssa_only_virtuals;
|
|
todo |= loop_invariant_motion_in_fun (cfun, false);
|
|
scev_reset ();
|
|
return todo;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
} // anon namespace
|
|
|
|
gimple_opt_pass *
|
|
make_pass_linterchange (gcc::context *ctxt)
|
|
{
|
|
return new pass_linterchange (ctxt);
|
|
}
|