[PATCH 5/5] Adds ipa-initcall-cp pass.
Erick Ochoa
erick.ochoa@theobroma-systems.com
Fri May 29 17:32:22 GMT 2020
This pass is a variant of constant propagation where global
primitive constants with a single write are propagated to multiple
read statements.
ChangeLog:
2020-05-20 Erick Ochoa <erick.ochoa@theobroma.systems.com>
gcc/Makefile.in: Adds new pass
gcc/passes.def: Same
gcc/tree-pass.h: Same
gcc/common.opt: Same
gcc/cgraph.h: Adds new field to cgraph_node
gcc/cgraph.c: Same
gcc/ipa-cp.c: May skip ipa-cp for a function
if initcall-cp has constants to propagate
in that same function
gcc/ipa-initcall-cp.c: New file
---
gcc/Makefile.in | 1 +
gcc/cgraph.h | 5 +-
gcc/common.opt | 8 +
gcc/ipa-cp.c | 2 +-
gcc/ipa-initcall-cp.c | 1199 +++++++++++++++++++++++++++++++++++++++++
gcc/passes.def | 1 +
gcc/tree-pass.h | 1 +
7 files changed, 1215 insertions(+), 2 deletions(-)
create mode 100644 gcc/ipa-initcall-cp.c
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 543b477ff18..b94fb633d78 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1401,6 +1401,7 @@ OBJS = \
internal-fn.o \
ipa-cp.o \
ipa-sra.o \
+ ipa-initcall-cp.o \
ipa-devirt.o \
ipa-fnsummary.o \
ipa-polymorphic-call.o \
diff --git a/gcc/cgraph.h b/gcc/cgraph.h
index 5ddeb65269b..532b4671609 100644
--- a/gcc/cgraph.h
+++ b/gcc/cgraph.h
@@ -937,7 +937,7 @@ struct GTY((tag ("SYMTAB_FUNCTION"))) cgraph_node :
public symtab_node
split_part (false), indirect_call_target (false), local (false),
versionable (false), can_change_signature (false),
redefined_extern_inline (false), tm_may_enter_irr (false),
- ipcp_clone (false), m_uid (uid), m_summary_id (-1)
+ ipcp_clone (false), skip_ipa_cp (false), m_uid (uid),
m_summary_id (-1)
{}
/* Remove the node from cgraph and all inline clones inlined into it.
@@ -1539,6 +1539,9 @@ struct GTY((tag ("SYMTAB_FUNCTION"))) cgraph_node
: public symtab_node
unsigned tm_may_enter_irr : 1;
/* True if this was a clone created by ipa-cp. */
unsigned ipcp_clone : 1;
+ /* True if ipa-initcall-cp and therefore we need to skip ipa-cp for
cgraph
+ * node. */
+ unsigned skip_ipa_cp : 1;
private:
/* Unique id of the node. */
diff --git a/gcc/common.opt b/gcc/common.opt
index d33383b523c..b2d8d1b0958 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -3408,4 +3408,12 @@ fipa-ra
Common Report Var(flag_ipa_ra) Optimization
Use caller save register across calls if possible.
+fipa-initcall-cp-dryrun
+Common Report Var(flag_initcall_cp_dryrun)
+Run analysis for propagating constants across initialization functions.
+
+fipa-initcall-cp
+Common Report Var(flag_initcall_cp) Optimization
+Run transformation for propagation constants across initialization
functions.
+
; This comment is to ensure we retain the blank line above.
diff --git a/gcc/ipa-cp.c b/gcc/ipa-cp.c
index c64e9104a94..1036cb0684e 100644
--- a/gcc/ipa-cp.c
+++ b/gcc/ipa-cp.c
@@ -1188,7 +1188,7 @@ initialize_node_lattices (struct cgraph_node *node)
gcc_checking_assert (node->has_gimple_body_p ());
- if (!ipa_get_param_count (info))
+ if (!ipa_get_param_count (info) || node->skip_ipa_cp)
disable = true;
else if (node->local)
{
diff --git a/gcc/ipa-initcall-cp.c b/gcc/ipa-initcall-cp.c
new file mode 100644
index 00000000000..02f70b7a908
--- /dev/null
+++ b/gcc/ipa-initcall-cp.c
@@ -0,0 +1,1199 @@
+/* Initcall constant propagation pass.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+
+ Contributed by Erick Ochoa <erick.ochoa@theobroma-systems.com>
+
+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 "tree.h"
+#include "tree-eh.h"
+#include "gimple.h"
+#include "gimple-expr.h"
+#include "gimple-iterator.h"
+#include "predict.h"
+#include "alloc-pool.h"
+#include "tree-pass.h"
+#include "cgraph.h"
+#include "diagnostic.h"
+#include "fold-const.h"
+#include "gimple-fold.h"
+#include "symbol-summary.h"
+#include "tree-vrp.h"
+#include "ipa-prop.h"
+#include "tree-pretty-print.h"
+#include "tree-inline.h"
+#include "ipa-fnsummary.h"
+#include "ipa-utils.h"
+#include "tree-ssa-ccp.h"
+#include "stringpool.h"
+#include "attribs.h"
+#include "gimple-pretty-print.h"
+#include "ssa.h"
+
+#define INITCALL_CP_SUFFIX "initcall.cp"
+
+typedef vec<struct ipa_ref *> ipa_ref_vec;
+
+/* log to dump file */
+static inline void
+log (const char *const fmt, ...)
+{
+ if (!dump_file)
+ return;
+
+ va_list args;
+ va_start (args, fmt);
+ vfprintf (dump_file, fmt, args);
+ va_end (args);
+}
+
+bool
+reach_nodes_via_bb1 (cgraph_node *n2);
+static bool
+bb1_dominates_bb2 (basic_block, basic_block, cgraph_node *);
+static bool
+write_before_call (struct ipa_ref *, struct ipa_ref *);
+static bool
+call_before_read (struct ipa_ref *, struct ipa_ref *);
+static hash_map<const char *, unsigned> *clone_num_suffixes1;
+static hash_map<cgraph_node *, cgraph_node *> *func_to_clone;
+static vec<struct cgraph_node *>
+get_nondominated_callees (cgraph_node *caller, cgraph_node *callee,
+ bool *exitEarly = NULL);
+
+static void
+load_function_body_of_ipa_ref (cgraph_node *node)
+{
+ gcc_assert (in_lto_p);
+ cgraph_node *f_cnode2 = node->ultimate_alias_target ();
+ const char *name = f_cnode2->dump_asm_name ();
+ log ("%s: for function '%s'\n", __func__, name);
+ if (dump_file)
+ {
+ dump_node (f_cnode2->decl, TDF_DETAILS, dump_file);
+ }
+ f_cnode2->get_untransformed_body ();
+}
+
+static void
+load_function_body_of_ipa_ref (struct ipa_ref *ref)
+{
+ /* IPA passes don't get the function bodies during LTO. */
+ gcc_assert (in_lto_p);
+
+ symtab_node *f_node = ref->referring;
+ cgraph_node *f_cnode = dyn_cast<cgraph_node *> (f_node);
+ load_function_body_of_ipa_ref (f_cnode);
+}
+
+static void
+dump_vnode_ipa_ref (ipa_ref *ref)
+{
+ if (!dump_file)
+ return;
+ log ("Reference type: %s\n", stringify_ipa_ref_use (ref->use));
+
+ symtab_node *f_node = ref->referring;
+ log ("Reference function: %s\n", f_node->dump_asm_name ());
+
+ log ("Gimple stmt (@0x%lx, lto_stmt_uid: %u):\n", (long) ref->stmt,
+ ref->lto_stmt_uid);
+ load_function_body_of_ipa_ref (ref);
+ print_gimple_stmt (dump_file, ref->stmt, 0, TDF_NONE);
+}
+
+/* Return true of all ops of assignment are constants. */
+static bool
+gimple_assign_is_single_const (gimple *stmt)
+{
+ tree op;
+
+ gcc_assert (gimple_code (stmt) == GIMPLE_ASSIGN);
+
+ if (gimple_has_volatile_ops (stmt))
+ {
+ log ("has volatile ops!\n");
+ return false;
+ }
+
+ if (gimple_num_ops (stmt) != 2)
+ {
+ log ("wrong num of ops: %u!\n", gimple_num_ops (stmt));
+ return false;
+ }
+
+ op = gimple_op (stmt, 1);
+ if (!tree_code_is_cst (op))
+ {
+ log ("op is not cst!\n");
+ return false;
+ }
+
+ return true;
+}
+
+/* Collects calls which are not dominated by callee */
+// assumptions:
+// * there is a callsite from caller to callee
+static vec<struct cgraph_node *>
+get_nondominated_callees (cgraph_node *caller, cgraph_node *callee,
+ bool *exitEarly)
+{
+ gcc_assert (in_lto_p);
+ caller->get_untransformed_body ();
+
+ function *func = DECL_STRUCT_FUNCTION (caller->decl);
+ basic_block bb;
+ bool found = false;
+ FOR_EACH_BB_FN (bb, func)
+ {
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb); !gsi_end_p (gsi);
+ gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+ if (gimple_code (stmt) != GIMPLE_CALL)
+ continue;
+
+ if (dump_file)
+ print_gimple_stmt (dump_file, stmt, 0, TDF_NONE);
+ tree rhs = gimple_op (stmt, 1);
+ if (TREE_CODE (rhs) == RECORD_TYPE)
+ {
+ gcc_assert (exitEarly);
+ *exitEarly = true;
+ return vNULL;
+ }
+ tree callee_decl = gimple_call_fndecl (stmt);
+ if (!callee_decl)
+ {
+ gcc_assert (exitEarly);
+ *exitEarly = true;
+ return vNULL;
+ }
+ cgraph_node *callee_node = cgraph_node::get (callee_decl);
+ if (callee_node != callee)
+ continue;
+
+ found = true;
+ }
+ if (found)
+ break;
+ }
+
+ gcc_assert (found);
+
+ // At this point in the program, we hold a valid bb.
+ // The callee is located in the bb
+ vec<struct cgraph_node *> callees = vNULL;
+ basic_block bb_c;
+ FOR_EACH_BB_FN (bb_c, func)
+ {
+ bool self_dominates = bb == bb_c;
+ bool bb_dominates_bbc = bb1_dominates_bb2 (bb, bb_c, caller);
+ if (bb_dominates_bbc && !self_dominates)
+ continue;
+
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb_c); !gsi_end_p
(gsi);
+ gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+ if (gimple_code (stmt) != GIMPLE_CALL)
+ continue;
+
+ tree callee_decl = gimple_call_fndecl (stmt);
+ cgraph_node *callee_node = cgraph_node::get (callee_decl);
+
+ if (fndecl_built_in_p (callee_node->decl))
+ continue;
+
+ if (self_dominates && callee_node == callee)
+ {
+ break;
+ }
+
+ callees.safe_push (callee_node);
+ }
+ }
+ return callees;
+}
+
+/* Returns true if bb1 dominates bb2
+ * Includes self-dominance */
+static bool
+bb1_dominates_bb2 (basic_block bb1, basic_block bb2, cgraph_node *cnode)
+{
+ // self dominance
+ if (bb1 == bb2)
+ return true;
+
+ push_cfun (DECL_STRUCT_FUNCTION (cnode->decl));
+
+ /* If dominator info is not available, we need to calculate it. */
+ if (!dom_info_available_p (CDI_DOMINATORS))
+ calculate_dominance_info (CDI_DOMINATORS);
+
+ /* Check if the read is dominated by the write. */
+ bool ret = dominated_by_p (CDI_DOMINATORS, bb2, bb1);
+
+ /* Restore cfun. */
+ pop_cfun ();
+
+ return ret;
+}
+
+/* Return true if write occurs before read.
+ * write and read must be located in the same function
+ * */
+static bool
+write_before_read_in_function (struct ipa_ref *write, struct ipa_ref *read)
+{
+ basic_block w_bb = gimple_bb (write->stmt);
+ basic_block r_bb = gimple_bb (read->stmt);
+
+ if (w_bb != r_bb)
+ {
+ /*
+ * The dominator framework operates on cfun.
+ * Therefore we need to set cfun accordingly.
+ */
+ symtab_node *w_node = write->referring;
+ cgraph_node *w_cnode = dyn_cast<cgraph_node *> (w_node);
+ return bb1_dominates_bb2 (w_bb, r_bb, w_cnode);
+ }
+
+ gimple_stmt_iterator gsi;
+ for (gsi = gsi_start_bb (w_bb); !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ if (gsi_stmt (gsi) == write->stmt)
+ return true;
+ if (gsi_stmt (gsi) == read->stmt)
+ return false;
+ }
+
+ /* Neither write nor read found it BB. */
+ gcc_unreachable ();
+ return false;
+}
+
+/*
+ * DFS over callees and return if callee is a or b.
+ */
+static cgraph_node *
+find_cgraph_in_callee_set (cgraph_node *n, hash_set<cgraph_node *> set,
+ cgraph_node *a, cgraph_node *b)
+{
+ cgraph_edge *cs;
+ for (cs = n->callees; cs; cs = cs->next_callee)
+ {
+ cgraph_node *callee = cs->callee->function_symbol ();
+ const char *const name = n->dump_asm_name ();
+ const char *const cname = callee->dump_asm_name ();
+ log ("Child of %s: %s\n", name, cname);
+ if (callee == a)
+ return a;
+ if (callee == b)
+ return b;
+ if (!set.contains (callee))
+ continue;
+ return find_cgraph_in_callee_set (callee, set, a, b);
+ }
+ return NULL;
+}
+
+/* Walks back along caller relations until main is found. */
+static cgraph_node *
+get_ancestor_main_dfs (hash_set<cgraph_node *> *visited,
+ vec<cgraph_node *> *path, cgraph_node *node)
+{
+ if (MAIN_NAME_P (DECL_NAME (node->decl)))
+ {
+ path->safe_push (node);
+ return node;
+ }
+
+ visited->add (node);
+
+ cgraph_edge *cs;
+ for (cs = node->callers; cs; cs = cs->next_caller)
+ {
+ cgraph_node *caller = cs->caller->function_symbol ();
+ if (visited->contains (caller))
+ continue;
+
+ cgraph_node *main = get_ancestor_main_dfs (visited, path, caller);
+
+ if (!main)
+ continue;
+
+ path->safe_push (node);
+ return main;
+ }
+ return NULL;
+}
+
+/* Returns path from main to cgraph_node in the vector */
+static cgraph_node *
+get_path_from_main_to (cgraph_node *node, vec<cgraph_node *> *path)
+{
+ hash_set<cgraph_node *> visited;
+ cgraph_node *main = get_ancestor_main_dfs (&visited, path, node);
+ visited.empty ();
+ return main;
+}
+
+/*
+ * Verifying that a stmt s1 is dominated by a stmt s2
+ * across function borders is not trivial with the available
+ * infrastructure (dominator algorithm on bb level plus cgraph).
+ * If we rule out external calls/callbacks, then we still need
+ * to guarantee a write on each possible path to the read.
+ *
+ * The implemented solution to this problem, which is of course
+ * too strict,
+ * but also not too compute/memory intensive is as follows:
+ *
+ * - Check if write is reachable by main () by only looking into
+ * the first bb in each function on the path.
+ * - All call stmts between main () and write must not possibly
+ * reach a read. We consider indirect call statements as
+ * possible reaching read (unless we can prove opposite).
+ *
+ * The external calls/callbacks are ruled out as follows:
+ *
+ * - all possible ancestors of read must not be external visible
+ * - all possible ancestors of read must not be function pointers
+ * - Something else?
+ */
+static bool
+write_before_read_across_function_borders (struct ipa_ref *write,
+ struct ipa_ref *read)
+{
+ symtab_node *w_node = write->referring;
+ cgraph_node *w_cnode = dyn_cast<cgraph_node *> (w_node);
+ symtab_node *r_node = read->referring;
+ cgraph_node *r_cnode = dyn_cast<cgraph_node *> (r_node);
+ cgraph_node *main;
+
+ /* Get main () function. */
+ vec<cgraph_node *> pw = vNULL;
+ main = get_path_from_main_to (w_cnode, &pw);
+ if (!main)
+ {
+ log ("main () is not ancestor of write -> skipping.\n");
+ return false;
+ }
+
+ vec<cgraph_node *> pr = vNULL;
+ cgraph_node *main_to_read = get_path_from_main_to (r_cnode, &pr);
+ if (!main_to_read)
+ {
+ log ("main () is not ancestor of read -> skipping.\n");
+ return false;
+ }
+
+ // Future work will involve looking at unconditional paths.
+ if (!reach_nodes_via_bb1 (w_cnode))
+ return false;
+
+ int i;
+ cgraph_node *node_in_pr;
+ FOR_EACH_VEC_ELT (pr, i, node_in_pr)
+ {
+ // I think main has to be externally visible.
+ if (node_in_pr == main)
+ continue;
+
+ /* Assure that all paths to read are not externally visible. */
+ if (cgraph_externally_visible_p (node_in_pr, flag_whole_program))
+ {
+ const char *const name = node_in_pr->dump_asm_name ();
+ log ("%s is externally visible... skipping\n", name);
+ return false;
+ }
+
+ /* Assure that all paths to read are not
+ * used as function pointers. */
+ if (node_in_pr->address_taken)
+ {
+ const char *const name = node_in_pr->dump_asm_name ();
+ log ("%s might be function pointer... skipping\n", name);
+ return false;
+ }
+ }
+
+ cgraph_node *caller = main;
+ cgraph_node *node_in_pw;
+ FOR_EACH_VEC_ELT (pw, i, node_in_pw)
+ {
+ gcc_assert (w_cnode != r_cnode);
+ if (node_in_pw == r_cnode && path_exists (r_cnode, w_cnode))
+ {
+ return call_before_read (write, read);
+ }
+
+ if (node_in_pw == w_cnode && path_exists (w_cnode, r_cnode))
+ {
+ return write_before_call (write, read);
+ }
+
+ if (node_in_pw == main)
+ {
+ continue;
+ }
+
+ const char *const cname = caller->dump_asm_name ();
+ const char *const nname = node_in_pw->dump_asm_name ();
+ log ("get_nondominated_callees from %s to %s\n", cname, nname);
+
+ bool exitEarly = false;
+ vec<cgraph_node *> non_dominated_callees
+ = get_nondominated_callees (caller, node_in_pw, &exitEarly);
+ if (exitEarly)
+ return false;
+ cgraph_node *non_dominated_callee;
+ int j;
+ FOR_EACH_VEC_ELT (non_dominated_callees, j, non_dominated_callee)
+ {
+ const char *const ndname = non_dominated_callee->dump_asm_name ();
+ log ("callee %d %s\n", j, ndname);
+ if (!path_exists (non_dominated_callee, r_cnode))
+ continue;
+
+ return false;
+ }
+
+ caller = node_in_pw;
+ }
+ return true;
+}
+
+/* Return callees accessible through bb1 */
+static vec<struct cgraph_node *>
+get_bb1_callees (cgraph_node *c, cgraph_node *w_cnode)
+{
+ vec<struct cgraph_node *> callees = vNULL;
+ if (fndecl_built_in_p (c->decl))
+ return vNULL;
+
+ const char *cname = c->dump_asm_name ();
+ log ("before get_untransformed_body %s\n", cname);
+
+ if (!c->definition)
+ return vNULL;
+
+ push_cfun (DECL_STRUCT_FUNCTION (c->decl));
+
+ gcc_assert (in_lto_p);
+ c->get_untransformed_body ();
+
+ pop_cfun ();
+
+ function *func = DECL_STRUCT_FUNCTION (c->decl);
+ /* Get first BB (after the fake entry BB). */
+ basic_block bb = ENTRY_BLOCK_PTR_FOR_FN (func)->next_bb;
+
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb); !gsi_end_p (gsi);
+ gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (gimple_code (stmt) != GIMPLE_CALL)
+ continue;
+
+ tree callee_decl = gimple_call_fndecl (stmt);
+ cgraph_node *callee_node = cgraph_node::get (callee_decl);
+ if (!path_exists (callee_node, w_cnode))
+ continue;
+
+ callees.safe_push (callee_node);
+ }
+
+ return callees;
+}
+
+/* Return true if n2 is reachable from n1 by visiting only first basic
blocks */
+static bool
+reach_nodes_via_bb1_dfs (hash_set<cgraph_node *> *visited, cgraph_node *n1,
+ cgraph_node *n2)
+{
+ if (n1 == n2)
+ return true;
+
+ visited->add (n1);
+
+ vec<struct cgraph_node *> callees = get_bb1_callees (n1, n2);
+
+ int i;
+ cgraph_node *callee;
+ FOR_EACH_VEC_ELT (callees, i, callee)
+ {
+ if (!visited->contains (callee))
+ {
+ bool found = reach_nodes_via_bb1_dfs (visited, callee, n2);
+ if (found)
+ {
+ callees.release ();
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/* Returns true if n2 is reachable from main function via following
call graph
+ * nodes reachable within first basic blocks */
+bool
+reach_nodes_via_bb1 (cgraph_node *n2)
+{
+ vec<cgraph_node *> pr = vNULL;
+ cgraph_node *main = get_path_from_main_to (n2, &pr);
+ hash_set<cgraph_node *> visited;
+ bool path_exists = reach_nodes_via_bb1_dfs (&visited, main, n2);
+ visited.empty ();
+ pr.release ();
+ return path_exists;
+}
+
+/* Determine if write occurs before a function call which contains read*/
+static bool
+write_before_call (struct ipa_ref *write, struct ipa_ref *read)
+{
+ symtab_node *w_node = write->referring;
+ cgraph_node *w_cnode = dyn_cast<cgraph_node *> (w_node);
+ symtab_node *r_node = read->referring;
+ cgraph_node *r_cnode = dyn_cast<cgraph_node *> (r_node);
+
+ gcc_assert (path_exists (w_cnode, r_cnode));
+ gcc_assert (w_cnode != r_cnode);
+
+ basic_block w_bb = gimple_bb (write->stmt);
+ basic_block r_bb = gimple_bb (read->stmt);
+
+ gcc_assert (in_lto_p);
+ w_cnode->get_untransformed_body ();
+
+ function *func = DECL_STRUCT_FUNCTION (w_cnode->decl);
+ basic_block c_bb;
+ vec<struct cgraph_node *> callees = vNULL;
+ FOR_EACH_BB_FN (c_bb, func)
+ {
+ bool self_dominates = w_bb == c_bb;
+ bool w_bb_dominates_c_bb = bb1_dominates_bb2 (w_bb, c_bb, w_cnode);
+ if (w_bb_dominates_c_bb && !self_dominates)
+ continue;
+
+ for (gimple_stmt_iterator gsi = gsi_start_bb (c_bb); !gsi_end_p
(gsi);
+ gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (stmt == write->stmt)
+ {
+ break;
+ }
+
+ if (gimple_code (stmt) != GIMPLE_CALL)
+ {
+ continue;
+ }
+ if (dump_file)
+ print_gimple_stmt (dump_file, stmt, 0, TDF_NONE);
+ tree rhs = gimple_op (stmt, 1);
+
+ if (rhs && TREE_CODE (rhs) == POINTER_TYPE)
+ return false;
+
+ tree callee_decl = gimple_call_fndecl (stmt);
+ if (!callee_decl)
+ return false;
+
+ cgraph_node *callee_node = cgraph_node::get (callee_decl);
+ const char *identifier
+ = IDENTIFIER_POINTER (DECL_NAME (callee_node->decl));
+ const char *sigsetjmp = "sigsetjmp";
+ if (strstr (identifier, sigsetjmp) != NULL)
+ return false;
+
+ if (fndecl_built_in_p (callee_node->decl))
+ continue;
+
+ log ("found callee %s\n", callee_node->dump_asm_name ());
+ callees.safe_push (callee_node);
+ }
+ }
+ cgraph_node *callee;
+ int i;
+ FOR_EACH_VEC_ELT (callees, i, callee)
+ {
+ if (path_exists (callee, r_cnode))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* Determine if call which contains write occurs before a read*/
+static bool
+call_before_read (struct ipa_ref *write, struct ipa_ref *read)
+{
+ symtab_node *w_node = write->referring;
+ cgraph_node *w_cnode = dyn_cast<cgraph_node *> (w_node);
+ symtab_node *r_node = read->referring;
+ cgraph_node *r_cnode = dyn_cast<cgraph_node *> (r_node);
+
+ gcc_assert (path_exists (r_cnode, w_cnode));
+ gcc_assert (w_cnode != r_cnode);
+
+ basic_block w_bb = gimple_bb (write->stmt);
+ basic_block r_bb = gimple_bb (read->stmt);
+
+ gcc_assert (in_lto_p);
+ r_cnode->get_untransformed_body ();
+
+ function *func = DECL_STRUCT_FUNCTION (r_cnode->decl);
+ basic_block c_bb;
+ FOR_EACH_BB_FN (c_bb, func)
+ {
+ bool self_dominates = c_bb == r_bb;
+ bool call_dominates_read = bb1_dominates_bb2 (c_bb, r_bb, r_cnode);
+ if (!call_dominates_read && !self_dominates)
+ continue;
+
+ for (gimple_stmt_iterator gsi = gsi_start_bb (c_bb); !gsi_end_p
(gsi);
+ gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ // self dominance
+ if (stmt == read->stmt)
+ break;
+
+ if (gimple_code (stmt) != GIMPLE_CALL)
+ continue;
+
+ if (dump_file)
+ print_gimple_stmt (dump_file, stmt, 0, TDF_NONE);
+ tree rhs = gimple_op (stmt, 1);
+ if (TREE_CODE (rhs) == POINTER_TYPE)
+ return false;
+ tree callee_decl = gimple_call_fndecl (stmt);
+
+ cgraph_node *callee_node = cgraph_node::get (callee_decl);
+ const char *identifier
+ = IDENTIFIER_POINTER (DECL_NAME (callee_node->decl));
+ const char *sigsetjmp = "sigsetjmp";
+ if (strstr (identifier, sigsetjmp) != NULL)
+ return false;
+
+ if (path_exists (callee_node, w_cnode))
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Determine if write occurs before a read*/
+static bool
+write_before_read (struct ipa_ref *write, struct ipa_ref *read)
+{
+ symtab_node *w_node = write->referring;
+ cgraph_node *w_cnode = dyn_cast<cgraph_node *> (w_node);
+ symtab_node *r_node = read->referring;
+ cgraph_node *r_cnode = dyn_cast<cgraph_node *> (r_node);
+
+ if (w_cnode == r_cnode)
+ /* Within the same function. */
+ return write_before_read_in_function (write, read);
+
+ /* Not within the same function. */
+ return write_before_read_across_function_borders (write, read);
+}
+
+/* Determine if write occurs before all reads*/
+static bool
+write_before_all_reads (struct ipa_ref *write, const ipa_ref_vec &reads)
+{
+ int i;
+ struct ipa_ref *read;
+
+ FOR_EACH_VEC_ELT (reads, i, read)
+ {
+ load_function_body_of_ipa_ref (read);
+ if (!write_before_read (write, read))
+ return false;
+ }
+ return true;
+}
+
+static void
+propagate_constant_to_read (tree write_val, struct ipa_ref *ref,
+ hash_set<cgraph_node *> *func)
+{
+ gcc_assert (ref->use == IPA_REF_LOAD);
+ load_function_body_of_ipa_ref (ref);
+
+ gimple *read_stmt = ref->stmt;
+
+ gcc_assert (gimple_code (read_stmt) == GIMPLE_ASSIGN);
+ gcc_assert (gimple_num_ops (read_stmt) == 2);
+
+ tree old_lhs = gimple_op (read_stmt, 0);
+ symtab_node *r_node = ref->referring;
+ cgraph_node *r_cnode = dyn_cast<cgraph_node *> (r_node);
+
+ cgraph_node **possible_clone = func_to_clone->get (r_cnode);
+ if (!possible_clone)
+ {
+ static const char *const suffix = INITCALL_CP_SUFFIX;
+ // callers has to be vNULL, otherwise, we will be
+ // analyzing clones...
+ // and we don't want that...
+ // but that means that we will need to update the callers
+ // later... in update_callgraph
+ cgraph_node *clone
+ = r_cnode->create_version_clone_with_body (vNULL, NULL, NULL, NULL,
+ NULL, suffix, NULL);
+ clone->get_untransformed_body ();
+ func_to_clone->put (r_cnode, clone);
+ }
+
+ possible_clone = func_to_clone->get (r_cnode);
+ cgraph_node *clone = *possible_clone;
+
+ // Build new stmt and replace old.
+ gimple_stmt_iterator gsi;
+ basic_block bb;
+ // Let's create a clone with body here...
+ // The clone should not have callers as
+ // to not interfere with the current
+ // analysis.
+ // The callers will be set at the end...
+
+ push_cfun (DECL_STRUCT_FUNCTION (clone->decl));
+ function *clone_func = DECL_STRUCT_FUNCTION (clone->decl);
+ bool found = false;
+ FOR_EACH_BB_FN (bb, clone_func)
+ {
+ for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+ if (gimple_code (stmt) != GIMPLE_ASSIGN)
+ continue;
+ tree old_val_clone = gimple_op (stmt, 1);
+ tree lhs = gimple_op (stmt, 0);
+
+ if (TREE_CODE (old_val_clone) != VAR_DECL)
+ continue;
+
+ tree old_val = gimple_op (read_stmt, 1);
+ if (IDENTIFIER_POINTER (DECL_NAME (old_val_clone))
+ != IDENTIFIER_POINTER (DECL_NAME (old_val)))
+ continue;
+
+ found = true;
+
+ gimple_stmt_iterator gsi2 = gsi_for_stmt (stmt);
+ tree new_lhs = make_ssa_name (TREE_TYPE (lhs));
+ gimple *new_read_stmt = gimple_build_assign (new_lhs, write_val);
+ gsi_insert_before (&gsi2, new_read_stmt, GSI_SAME_STMT);
+
+ gimple *use_stmt;
+ imm_use_iterator use_iter;
+ FOR_EACH_IMM_USE_STMT (use_stmt, use_iter, lhs)
+ {
+ use_operand_p use_p;
+ FOR_EACH_IMM_USE_ON_STMT (use_p, use_iter)
+ SET_USE (use_p, new_lhs);
+ update_stmt (use_stmt);
+ }
+ }
+
+ if (found)
+ break;
+ }
+ gcc_assert (found);
+ pop_cfun ();
+}
+
+static bool
+ipa_initcall_get_writes_and_reads (varpool_node *vnode, ipa_ref_vec
*writes,
+ ipa_ref_vec *reads)
+{
+ int i;
+ struct ipa_ref *ref;
+
+ log ("%s for variable '%s'.\n", __func__, vnode->name ());
+
+ /* Only IPA_REF_STORE and IPA_REF_LOAD left. */
+ for (i = 0; vnode->iterate_referring (i, ref); i++)
+ {
+ symtab_node *f_node = ref->referring;
+ cgraph_node *f_cnode = dyn_cast<cgraph_node *> (f_node);
+
+ if (!f_cnode)
+ {
+ log ("skipping variable %s due to static initialization\n",
+ vnode->name ());
+ return false;
+ }
+ // it is possible that f_cnode is NULL if the dyn_cast fails.
+ // If the dyn_cast fails, this is an example of static
initialization.
+ const char *identifier = IDENTIFIER_POINTER (DECL_NAME
(f_cnode->decl));
+ static const char *const suffix = INITCALL_CP_SUFFIX;
+ if (strstr (identifier, suffix) != NULL)
+ continue;
+
+ if (ref->use == IPA_REF_STORE)
+ writes->safe_push (ref);
+
+ if (ref->use == IPA_REF_LOAD)
+ reads->safe_push (ref);
+ }
+ return true;
+}
+
+static void
+propagate_constant_to_reads (tree write_val, const ipa_ref_vec
&reads_original,
+ hash_set<cgraph_node *> *funcs)
+{
+ /* Iterate over reads and replace them by constant. */
+ struct ipa_ref *ref;
+ int i;
+ FOR_EACH_VEC_ELT (reads_original, i, ref)
+ {
+ propagate_constant_to_read (write_val, ref, funcs);
+ }
+}
+
+/*
+ * Extracts the assigned constant, iff the given statement
+ * is a constant assignment. Returns NULL_TREE otherwise.
+ */
+static tree
+extract_constant_from_initcall_write (struct ipa_ref *write)
+{
+ symtab_node *f_node = write->referring;
+ cgraph_node *f_cnode = dyn_cast<cgraph_node *> (f_node);
+
+ tree decl = f_cnode->decl;
+ if (TREE_CODE (decl) != FUNCTION_DECL)
+ {
+ log ("Not function decl -> skipping.\n");
+ return NULL_TREE;
+ }
+
+ if (!f_cnode->has_gimple_body_p ())
+ log ("Does NOT have gimple body!\n");
+ if (f_cnode->inlined_to)
+ log ("Inlined To\n");
+
+ log ("%s: for writer gimple:\n", __func__);
+ dump_vnode_ipa_ref (write);
+
+ /* Get the write function's body. */
+ load_function_body_of_ipa_ref (write);
+
+ gimple *stmt = write->stmt;
+
+ /* Verify that we have an assignment. */
+ if (gimple_code (stmt) != GIMPLE_ASSIGN)
+ {
+ log ("Write stmt not assignment -> skipping.\n");
+ return NULL_TREE;
+ }
+
+ /* Check if write's LHS (vnode) is not volatile. */
+ tree lhs = gimple_assign_lhs (stmt);
+ if (TREE_THIS_VOLATILE (TREE_TYPE (lhs)))
+ {
+ log ("Variable volatile -> skipping.\n");
+ return NULL_TREE;
+ }
+
+ /* Check if RHS of write stmt is constant. */
+ if (!gimple_assign_is_single_const (stmt))
+ {
+ log ("Found non-const operands.\n");
+ return NULL_TREE;
+ }
+
+ /* Extract the constant. */
+ tree write_val = gimple_op (stmt, 1);
+
+ if (dump_file)
+ {
+ log ("Const op:\n");
+ dump_node (write_val, TDF_DETAILS, dump_file);
+ }
+
+ return write_val;
+}
+
+static void
+ipa_initcall_cp_execute_for_var (varpool_node *vnode,
+ hash_set<cgraph_node *> *update_functions)
+{
+ ipa_ref_vec writes = vNULL;
+ ipa_ref_vec reads = vNULL;
+ struct ipa_ref *write;
+ tree write_val;
+ gimple_stmt_iterator gsi;
+ bool remove_permanently;
+
+ log ("%s for variable '%s'.\n", __func__, vnode->name ());
+
+ if (dump_file)
+ {
+ dump_node (vnode->decl, TDF_DETAILS, dump_file);
+ }
+
+ /* Variable must not be externally visible. */
+ if (vnode->externally_visible_p ())
+ {
+ log ("\texternally visible -> skipping\n");
+ return;
+ }
+
+ /* All refs must be explicit. */
+ if (!vnode->all_refs_explicit_p ())
+ {
+ log ("Not explicit variable refs -> skipping.\n");
+ return;
+ }
+
+ /* Check if any ref->use is IPA_REF_ALIAS. */
+ if (vnode->has_aliases_p ())
+ {
+ log ("Found IPA_REF_ALIAS -> skipping.\n");
+ return;
+ }
+
+ /* Check if any ref->use is IPA_REF_ADDR. */
+ if (vnode->alias || vnode->address_matters_p ())
+ {
+ log ("Found IPA_REF_ADDR -> skipping.\n");
+ return;
+ }
+
+ /* We don't touch arrays. */
+ if (TREE_CODE (TREE_TYPE (vnode->decl)) == ARRAY_TYPE)
+ {
+ log ("Variable is array -> skipping.\n");
+ return;
+ }
+
+ /* We don't touch structs. */
+ if (TREE_CODE (TREE_TYPE (vnode->decl)) == RECORD_TYPE)
+ {
+ log ("Variable is struct -> skipping.\n");
+ return;
+ }
+
+ /* We don't touch unions. */
+ if (TREE_CODE (TREE_TYPE (vnode->decl)) == UNION_TYPE)
+ {
+ log ("Variable is union -> skipping.\n");
+ return;
+ }
+
+ /* Get all refs (must be REF_STORE or REF_LOAD). */
+ bool continue_work
+ = ipa_initcall_get_writes_and_reads (vnode, &writes, &reads);
+ if (!continue_work)
+ {
+ log ("Static initialization -> skipping.\n");
+ writes.release ();
+ reads.release ();
+ return;
+ }
+
+ if (writes.length () > 1)
+ {
+ /* More than one writer. */
+ log ("More than one writer -> skipping.\n");
+ writes.release ();
+ reads.release ();
+ return;
+ }
+ else if (writes.length () < 1)
+ {
+ /* No writer. */
+ log ("No writer -> skipping.\n");
+ writes.release ();
+ reads.release ();
+ return;
+ }
+
+ /*
+ * Note:
+ * Limiting ourselves to only one write is not necessary in general,
+ * but good enough to address the typical init () case.
+ * Big advantage is, that it makes the following code much easier.
+ */
+
+ /* Get (only) write ref. */
+ write = writes.pop ();
+
+ /* Check if write's RHS is a constant and get it. */
+ write_val = extract_constant_from_initcall_write (write);
+ if (write_val == NULL_TREE)
+ {
+ log ("Write's RHS is not constant -> skipping.\n");
+ writes.release ();
+ reads.release ();
+ return;
+ }
+
+ /* Assure all reads are after the write. */
+ if (!write_before_all_reads (write, reads))
+ {
+ log ("Write not guaranteed to be before read -> skipping.\n");
+ writes.release ();
+ reads.release ();
+ return;
+ }
+
+ /* Propagate constant to reads. */
+ if (!flag_initcall_cp_dryrun)
+ propagate_constant_to_reads (write_val, reads, update_functions);
+
+ /* Finally remove the write. */
+ gsi = gsi_for_stmt (write->stmt);
+ remove_permanently = false; // XXX: fails with true?
+ // gsi_remove (&gsi, remove_permanently);
+
+ log ("Eliminated variable '%s'.\n\n", vnode->name ());
+
+ writes.release ();
+ reads.release ();
+}
+
+/* Replace functions with clones */
+bool
+update_callgraph (cgraph_node *const &r_cnode, cgraph_node **clone_ptr,
void *)
+{
+ vec<cgraph_edge *> callers = r_cnode->collect_callers ();
+ cgraph_node *clone = *clone_ptr;
+ cgraph_edge *e;
+ int i;
+ profile_count count = r_cnode->count;
+
+ FOR_EACH_VEC_ELT (callers, i, e)
+ e->redirect_callee (clone);
+
+ for (e = clone->callers; e; e = e->next_caller)
+ {
+ e->caller->get_untransformed_body ();
+ function *inner_function = DECL_STRUCT_FUNCTION (e->caller->decl);
+ gimple_call_set_fndecl (e->call_stmt, clone->decl);
+ maybe_clean_eh_stmt_fn (inner_function, e->call_stmt);
+ }
+
+ r_cnode->skip_ipa_cp = true;
+ push_cfun (DECL_STRUCT_FUNCTION (r_cnode->decl));
+
+ log ("dumping function %s\n", r_cnode->dump_asm_name ());
+ function *func = DECL_STRUCT_FUNCTION (r_cnode->decl);
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, func)
+ {
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb); !gsi_end_p (gsi);
+ gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+ if (dump_file)
+ print_gimple_stmt (dump_file, stmt, 0, TDF_NONE);
+ }
+ }
+ if (dom_info_available_p (CDI_DOMINATORS))
+ free_dominance_info (CDI_DOMINATORS);
+ pop_cfun ();
+ return true;
+}
+
+static unsigned int
+ipa_initcall_cp_execute (void)
+{
+ varpool_node *vnode;
+
+ clone_num_suffixes1 = new hash_map<const char *, unsigned>;
+ hash_set<cgraph_node *> update_functions;
+ func_to_clone = new hash_map<cgraph_node *, cgraph_node *>;
+ FOR_EACH_VARIABLE (vnode)
+ {
+ ipa_initcall_cp_execute_for_var (vnode, &update_functions);
+ }
+
+ if (!flag_initcall_cp_dryrun)
+ func_to_clone->traverse<void *, update_callgraph> (NULL);
+
+ delete clone_num_suffixes1;
+ delete func_to_clone;
+
+ return 0;
+}
+
+namespace {
+
+const pass_data pass_data_ipa_initcall_cp = {
+ SIMPLE_IPA_PASS,
+ "initcall-cp",
+ OPTGROUP_NONE,
+ TV_NONE,
+ (PROP_cfg | PROP_ssa),
+ 0,
+ 0,
+ 0,
+ (TODO_update_ssa | TODO_cleanup_cfg | TODO_dump_symtab
+ | TODO_rebuild_cgraph_edges | TODO_discard_function),
+};
+
+class pass_ipa_initcall_cp : public ipa_opt_pass_d
+{
+public:
+ pass_ipa_initcall_cp (gcc::context *ctxt)
+ : ipa_opt_pass_d (pass_data_ipa_initcall_cp, ctxt, NULL, NULL,
NULL, NULL,
+ NULL, NULL, 0, NULL, NULL)
+ {}
+
+ /* opt_pass methods: */
+ virtual bool gate (function *)
+ {
+ return in_lto_p && (flag_initcall_cp || flag_initcall_cp_dryrun);
+ }
+
+ virtual unsigned int execute (function *)
+ {
+ return ipa_initcall_cp_execute ();
+ }
+
+}; // class pass_ipa_initcall_cp
+
+} // namespace
+
+ipa_opt_pass_d *
+make_pass_ipa_initcall_cp (gcc::context *ctxt)
+{
+ return new pass_ipa_initcall_cp (ctxt);
+}
diff --git a/gcc/passes.def b/gcc/passes.def
index 2bf2cb78fc5..62609951bac 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -149,6 +149,7 @@ along with GCC; see the file COPYING3. If not see
NEXT_PASS (pass_ipa_profile);
NEXT_PASS (pass_ipa_icf);
NEXT_PASS (pass_ipa_devirt);
+ NEXT_PASS (pass_ipa_initcall_cp);
NEXT_PASS (pass_ipa_cp);
NEXT_PASS (pass_ipa_sra);
NEXT_PASS (pass_ipa_cdtor_merge);
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index a1207a20a3c..65e09c657b7 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -501,6 +501,7 @@ extern ipa_opt_pass_d *make_pass_ipa_fn_summary
(gcc::context *ctxt);
extern ipa_opt_pass_d *make_pass_ipa_inline (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_free_lang_data (gcc::context
*ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_free_fn_summary
(gcc::context *ctxt);
+extern ipa_opt_pass_d *make_pass_ipa_initcall_cp (gcc::context *ctxt);
extern ipa_opt_pass_d *make_pass_ipa_cp (gcc::context *ctxt);
extern ipa_opt_pass_d *make_pass_ipa_sra (gcc::context *ctxt);
extern ipa_opt_pass_d *make_pass_ipa_icf (gcc::context *ctxt);
--
2.18.1
More information about the Gcc-patches
mailing list