2388 lines
72 KiB
Diff
2388 lines
72 KiB
Diff
|
|
From b73462757734c62f64e7a4379340679ec6f19669 Mon Sep 17 00:00:00 2001
|
||
|
|
From: Diachkov Ilia <diachkov.ilia1@huawei-partners.com>
|
||
|
|
Date: Tue, 27 Feb 2024 07:28:12 +0800
|
||
|
|
Subject: [PATCH 06/18] Port icp patch to GCC 12
|
||
|
|
|
||
|
|
---
|
||
|
|
gcc/common.opt | 8 +
|
||
|
|
gcc/dbgcnt.def | 1 +
|
||
|
|
gcc/ipa-devirt.cc | 1855 +++++++++++++++++++++++++++++++++++
|
||
|
|
gcc/passes.def | 1 +
|
||
|
|
gcc/testsuite/gcc.dg/icp1.c | 40 +
|
||
|
|
gcc/testsuite/gcc.dg/icp2.c | 38 +
|
||
|
|
gcc/testsuite/gcc.dg/icp3.c | 52 +
|
||
|
|
gcc/testsuite/gcc.dg/icp4.c | 55 ++
|
||
|
|
gcc/testsuite/gcc.dg/icp5.c | 66 ++
|
||
|
|
gcc/testsuite/gcc.dg/icp6.c | 66 ++
|
||
|
|
gcc/testsuite/gcc.dg/icp7.c | 48 +
|
||
|
|
gcc/timevar.def | 1 +
|
||
|
|
gcc/tree-pass.h | 1 +
|
||
|
|
13 files changed, 2232 insertions(+)
|
||
|
|
create mode 100644 gcc/testsuite/gcc.dg/icp1.c
|
||
|
|
create mode 100644 gcc/testsuite/gcc.dg/icp2.c
|
||
|
|
create mode 100644 gcc/testsuite/gcc.dg/icp3.c
|
||
|
|
create mode 100644 gcc/testsuite/gcc.dg/icp4.c
|
||
|
|
create mode 100644 gcc/testsuite/gcc.dg/icp5.c
|
||
|
|
create mode 100644 gcc/testsuite/gcc.dg/icp6.c
|
||
|
|
create mode 100644 gcc/testsuite/gcc.dg/icp7.c
|
||
|
|
|
||
|
|
diff --git a/gcc/common.opt b/gcc/common.opt
|
||
|
|
index 39c90604e..16aadccf6 100644
|
||
|
|
--- a/gcc/common.opt
|
||
|
|
+++ b/gcc/common.opt
|
||
|
|
@@ -1316,6 +1316,14 @@ fdevirtualize
|
||
|
|
Common Var(flag_devirtualize) Optimization
|
||
|
|
Try to convert virtual calls to direct ones.
|
||
|
|
|
||
|
|
+ficp
|
||
|
|
+Common Var(flag_icp) Optimization Init(0)
|
||
|
|
+Try to promote indirect calls to direct ones.
|
||
|
|
+
|
||
|
|
+ficp-speculatively
|
||
|
|
+Common Var(flag_icp_speculatively) Optimization
|
||
|
|
+Promote indirect calls speculatively.
|
||
|
|
+
|
||
|
|
fdiagnostics-show-location=
|
||
|
|
Common Joined RejectNegative Enum(diagnostic_prefixing_rule)
|
||
|
|
-fdiagnostics-show-location=[once|every-line] How often to emit source location at the beginning of line-wrapped diagnostics.
|
||
|
|
diff --git a/gcc/dbgcnt.def b/gcc/dbgcnt.def
|
||
|
|
index 3aa18cd0c..a00bbc31b 100644
|
||
|
|
--- a/gcc/dbgcnt.def
|
||
|
|
+++ b/gcc/dbgcnt.def
|
||
|
|
@@ -170,6 +170,7 @@ DEBUG_COUNTER (graphite_scop)
|
||
|
|
DEBUG_COUNTER (hoist)
|
||
|
|
DEBUG_COUNTER (hoist_insn)
|
||
|
|
DEBUG_COUNTER (ia64_sched2)
|
||
|
|
+DEBUG_COUNTER (icp)
|
||
|
|
DEBUG_COUNTER (if_after_combine)
|
||
|
|
DEBUG_COUNTER (if_after_reload)
|
||
|
|
DEBUG_COUNTER (if_conversion)
|
||
|
|
diff --git a/gcc/ipa-devirt.cc b/gcc/ipa-devirt.cc
|
||
|
|
index 74fe65608..383839189 100644
|
||
|
|
--- a/gcc/ipa-devirt.cc
|
||
|
|
+++ b/gcc/ipa-devirt.cc
|
||
|
|
@@ -103,9 +103,14 @@ along with GCC; see the file COPYING3. If not see
|
||
|
|
indirect polymorphic edge all possible polymorphic call targets of the call.
|
||
|
|
|
||
|
|
pass_ipa_devirt performs simple speculative devirtualization.
|
||
|
|
+ pass_ipa_icp performs simple indirect call promotion.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "config.h"
|
||
|
|
+#define INCLUDE_ALGORITHM
|
||
|
|
+#define INCLUDE_SET
|
||
|
|
+#define INCLUDE_MAP
|
||
|
|
+#define INCLUDE_LIST
|
||
|
|
#include "system.h"
|
||
|
|
#include "coretypes.h"
|
||
|
|
#include "backend.h"
|
||
|
|
@@ -127,6 +132,7 @@ along with GCC; see the file COPYING3. If not see
|
||
|
|
#include "ipa-fnsummary.h"
|
||
|
|
#include "demangle.h"
|
||
|
|
#include "dbgcnt.h"
|
||
|
|
+#include "gimple-iterator.h"
|
||
|
|
#include "gimple-pretty-print.h"
|
||
|
|
#include "intl.h"
|
||
|
|
#include "stringpool.h"
|
||
|
|
@@ -4401,5 +4407,1854 @@ make_pass_ipa_odr (gcc::context *ctxt)
|
||
|
|
return new pass_ipa_odr (ctxt);
|
||
|
|
}
|
||
|
|
|
||
|
|
+/* Function signature map used to look up function decl which corresponds to
|
||
|
|
+ the given function type. */
|
||
|
|
+typedef std::set<unsigned> type_set;
|
||
|
|
+typedef std::set<tree> decl_set;
|
||
|
|
+typedef std::map<unsigned, type_set*> type_alias_map;
|
||
|
|
+typedef std::map<unsigned, decl_set*> type_decl_map;
|
||
|
|
+typedef std::map<unsigned, tree> uid_to_type_map;
|
||
|
|
+typedef std::map<tree, tree> type_map;
|
||
|
|
+
|
||
|
|
+static bool has_address_taken_functions_with_varargs = false;
|
||
|
|
+static type_set *unsafe_types = NULL;
|
||
|
|
+static type_alias_map *fta_map = NULL;
|
||
|
|
+static type_alias_map *ta_map = NULL;
|
||
|
|
+static type_map *ctype_map = NULL;
|
||
|
|
+static type_alias_map *cbase_to_ptype = NULL;
|
||
|
|
+static type_decl_map *fs_map = NULL;
|
||
|
|
+static uid_to_type_map *type_uid_map = NULL;
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+print_type_set(unsigned ftype_uid, type_alias_map *map)
|
||
|
|
+{
|
||
|
|
+ if (!map->count (ftype_uid))
|
||
|
|
+ return;
|
||
|
|
+ type_set* s = (*map)[ftype_uid];
|
||
|
|
+ for (type_set::const_iterator it = s->begin (); it != s->end (); it++)
|
||
|
|
+ fprintf (dump_file, it == s->begin () ? "%d" : ", %d", *it);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_type_with_uid (const char *msg, tree type, dump_flags_t flags = TDF_NONE)
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, msg);
|
||
|
|
+ print_generic_expr (dump_file, type, flags);
|
||
|
|
+ fprintf (dump_file, " (%d)\n", TYPE_UID (type));
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Walk aggregate type and collect types of scalar elements. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+collect_scalar_types (tree tp, std::list<tree> &types)
|
||
|
|
+{
|
||
|
|
+ /* TODO: take into account different field offsets.
|
||
|
|
+ Also support array casts. */
|
||
|
|
+ if (tp && dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_type_with_uid ("Walk var's type: ", tp, TDF_UID);
|
||
|
|
+ if (RECORD_OR_UNION_TYPE_P (tp))
|
||
|
|
+ {
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Record's fields {\n");
|
||
|
|
+ for (tree field = TYPE_FIELDS (tp); field;
|
||
|
|
+ field = DECL_CHAIN (field))
|
||
|
|
+ {
|
||
|
|
+ if (TREE_CODE (field) != FIELD_DECL)
|
||
|
|
+ continue;
|
||
|
|
+ collect_scalar_types (TREE_TYPE (field), types);
|
||
|
|
+ }
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "}\n");
|
||
|
|
+ return;
|
||
|
|
+ }
|
||
|
|
+ if (TREE_CODE (tp) == ARRAY_TYPE)
|
||
|
|
+ {
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Array's innermost type:\n");
|
||
|
|
+ /* Take the innermost component type. */
|
||
|
|
+ tree elt;
|
||
|
|
+ for (elt = TREE_TYPE (tp); TREE_CODE (elt) == ARRAY_TYPE;
|
||
|
|
+ elt = TREE_TYPE (elt))
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ print_generic_expr (dump_file, elt);
|
||
|
|
+ collect_scalar_types (elt, types);
|
||
|
|
+ return;
|
||
|
|
+ }
|
||
|
|
+ types.push_back (tp);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void maybe_register_aliases (tree type1, tree type2);
|
||
|
|
+
|
||
|
|
+/* Walk type lists and maybe register type aliases. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+compare_type_lists (std::list<tree> tlist1, std::list<tree> tlist2)
|
||
|
|
+{
|
||
|
|
+ for (std::list<tree>::iterator ti1 = tlist1.begin (), ti2 = tlist2.begin ();
|
||
|
|
+ ti1 != tlist1.end (); ++ti1, ++ti2)
|
||
|
|
+ {
|
||
|
|
+ /* TODO: correct the analysis results if lists have different length. */
|
||
|
|
+ if (ti2 == tlist2.end ())
|
||
|
|
+ {
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Type lists with different length!\n");
|
||
|
|
+ break;
|
||
|
|
+ }
|
||
|
|
+ maybe_register_aliases (*ti1, *ti2);
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* For two given types collect scalar element types and
|
||
|
|
+ compare the result lists to find type aliases. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+collect_scalar_types_and_find_aliases (tree t1, tree t2)
|
||
|
|
+{
|
||
|
|
+ std::list<tree> tlist1;
|
||
|
|
+ std::list<tree> tlist2;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "First type list: ");
|
||
|
|
+ collect_scalar_types (t1, tlist1);
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Second type list: ");
|
||
|
|
+ collect_scalar_types (t2, tlist2);
|
||
|
|
+ compare_type_lists (tlist1, tlist2);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Dump type with the corresponding set from the map. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_type_uid_with_set (const char *msg, tree type, type_alias_map *map,
|
||
|
|
+ bool dump_type = true, bool with_newline = true)
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, msg, TYPE_UID (type));
|
||
|
|
+ if (dump_type)
|
||
|
|
+ print_generic_expr (dump_file, type);
|
||
|
|
+ fprintf (dump_file, " (");
|
||
|
|
+ print_type_set (TYPE_UID (type), map);
|
||
|
|
+ fprintf (dump_file, ")");
|
||
|
|
+ fprintf (dump_file, with_newline ? "\n" : " ");
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_two_types_uids_with_set (const char *msg, unsigned t1_uid,
|
||
|
|
+ unsigned t2_uid, type_alias_map *map)
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, msg, t1_uid, t2_uid);
|
||
|
|
+ fprintf (dump_file, " (");
|
||
|
|
+ print_type_set (t1_uid, map);
|
||
|
|
+ fprintf (dump_file, ")\n");
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Register type aliases in the map. Return true if new alias
|
||
|
|
+ is registered. */
|
||
|
|
+
|
||
|
|
+static bool
|
||
|
|
+register_ailas_type (tree type, tree alias_type, type_alias_map *map,
|
||
|
|
+ bool only_merge = false)
|
||
|
|
+{
|
||
|
|
+ /* TODO: maybe support the case with one missed type. */
|
||
|
|
+ if (!type || !alias_type)
|
||
|
|
+ return false;
|
||
|
|
+ unsigned type_uid = TYPE_UID (type);
|
||
|
|
+ unsigned alias_type_uid = TYPE_UID (alias_type);
|
||
|
|
+ if (type_uid_map->count (type_uid) == 0)
|
||
|
|
+ (*type_uid_map)[type_uid] = type;
|
||
|
|
+ if (type_uid_map->count (alias_type_uid) == 0)
|
||
|
|
+ (*type_uid_map)[alias_type_uid] = alias_type;
|
||
|
|
+
|
||
|
|
+ if (map->count (type_uid) == 0 && map->count (alias_type_uid) == 0)
|
||
|
|
+ {
|
||
|
|
+ (*map)[type_uid] = new type_set ();
|
||
|
|
+ (*map)[alias_type_uid] = (*map)[type_uid];
|
||
|
|
+ }
|
||
|
|
+ else if (map->count (type_uid) == 0)
|
||
|
|
+ (*map)[type_uid] = (*map)[alias_type_uid];
|
||
|
|
+ else if (map->count (alias_type_uid) == 0)
|
||
|
|
+ (*map)[alias_type_uid] = (*map)[type_uid];
|
||
|
|
+ else if (map->count (type_uid) && map->count (alias_type_uid))
|
||
|
|
+ {
|
||
|
|
+ if ((*map)[type_uid] == (*map)[alias_type_uid])
|
||
|
|
+ {
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_two_types_uids_with_set ("Types (%d) and (%d) are already in",
|
||
|
|
+ type_uid, alias_type_uid, map);
|
||
|
|
+ return false;
|
||
|
|
+ }
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ {
|
||
|
|
+ dump_type_uid_with_set ("T1 (%d) in set", type, map, false, true);
|
||
|
|
+ dump_type_uid_with_set ("T2 (%d) in set", alias_type, map,
|
||
|
|
+ false, true);
|
||
|
|
+ }
|
||
|
|
+ (*map)[type_uid]->insert ((*map)[alias_type_uid]->begin (),
|
||
|
|
+ (*map)[alias_type_uid]->end ());
|
||
|
|
+ type_set *type_set = (*map)[alias_type_uid];
|
||
|
|
+ for (type_set::const_iterator it1 = type_set->begin ();
|
||
|
|
+ it1 != type_set->end (); ++it1)
|
||
|
|
+ (*map)[*it1] = (*map)[type_uid];
|
||
|
|
+ delete type_set;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "MERGE: ");
|
||
|
|
+ }
|
||
|
|
+ if (!only_merge)
|
||
|
|
+ {
|
||
|
|
+ (*map)[type_uid]->insert (alias_type_uid);
|
||
|
|
+ (*map)[type_uid]->insert (type_uid);
|
||
|
|
+ }
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_two_types_uids_with_set ("Insert types (%d) and (%d) into set",
|
||
|
|
+ type_uid, alias_type_uid, map);
|
||
|
|
+ return true;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_two_types_with_uids (const char *msg, tree t1, tree t2)
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, msg);
|
||
|
|
+ print_generic_expr (dump_file, t1, TDF_UID);
|
||
|
|
+ fprintf (dump_file, " (%d), ", TYPE_UID (t1));
|
||
|
|
+ print_generic_expr (dump_file, t2, TDF_UID);
|
||
|
|
+ fprintf (dump_file, " (%d)\n", TYPE_UID (t2));
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+analyze_pointees (tree type1, tree type2)
|
||
|
|
+{
|
||
|
|
+ gcc_assert (POINTER_TYPE_P (type1) && POINTER_TYPE_P (type2));
|
||
|
|
+ tree base1 = TREE_TYPE (type1);
|
||
|
|
+ tree base2 = TREE_TYPE (type2);
|
||
|
|
+ /* TODO: maybe analyze void pointers. */
|
||
|
|
+ if (VOID_TYPE_P(base1) || VOID_TYPE_P(base2))
|
||
|
|
+ return;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_two_types_with_uids ("Walk pointee types: ", base1, base2);
|
||
|
|
+ collect_scalar_types_and_find_aliases (base1, base2);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+map_canonical_base_to_pointer (tree type, tree to_insert)
|
||
|
|
+{
|
||
|
|
+ type = TYPE_MAIN_VARIANT (type);
|
||
|
|
+ tree base_type = TREE_TYPE (type);
|
||
|
|
+ tree cbase_type = TYPE_CANONICAL (base_type);
|
||
|
|
+ if (!cbase_type)
|
||
|
|
+ return;
|
||
|
|
+ unsigned cbase_type_uid = TYPE_UID (cbase_type);
|
||
|
|
+ if (type_uid_map->count (cbase_type_uid) == 0)
|
||
|
|
+ (*type_uid_map)[cbase_type_uid] = cbase_type;
|
||
|
|
+
|
||
|
|
+ if (cbase_to_ptype->count (cbase_type_uid) == 0)
|
||
|
|
+ {
|
||
|
|
+ (*cbase_to_ptype)[cbase_type_uid] = new type_set ();
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "New map cb-to-p=(%d): ", cbase_type_uid);
|
||
|
|
+ }
|
||
|
|
+ else if (!(*cbase_to_ptype)[cbase_type_uid]->count (TYPE_UID (to_insert)))
|
||
|
|
+ {
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Found map cb-to-p=(%d): ", cbase_type_uid);
|
||
|
|
+ }
|
||
|
|
+ else
|
||
|
|
+ return;
|
||
|
|
+ /* Add all variants of 'to_insert' type. */
|
||
|
|
+ for (tree t = to_insert; t; t = TYPE_NEXT_VARIANT (t))
|
||
|
|
+ {
|
||
|
|
+ unsigned t_uid = TYPE_UID (t);
|
||
|
|
+ if (!(*cbase_to_ptype)[cbase_type_uid]->count (t_uid))
|
||
|
|
+ {
|
||
|
|
+ (*cbase_to_ptype)[cbase_type_uid]->insert (t_uid);
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "(%d) ", t_uid);
|
||
|
|
+ }
|
||
|
|
+ if (type_uid_map->count (t_uid) == 0)
|
||
|
|
+ (*type_uid_map)[t_uid] = t;
|
||
|
|
+ }
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "\n");
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Analyse two types and maybe register them as aliases. Also collect
|
||
|
|
+ unsafe function types and map canonical base types to corresponding
|
||
|
|
+ pointer types. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+maybe_register_aliases (tree type1, tree type2)
|
||
|
|
+{
|
||
|
|
+ if (type1 && POINTER_TYPE_P (type1) && !FUNCTION_POINTER_TYPE_P (type1))
|
||
|
|
+ map_canonical_base_to_pointer (type1, type1);
|
||
|
|
+ if (type2 && POINTER_TYPE_P (type2) && !FUNCTION_POINTER_TYPE_P (type2))
|
||
|
|
+ map_canonical_base_to_pointer (type2, type2);
|
||
|
|
+
|
||
|
|
+ if (type1 == type2 || !type1 || !type2)
|
||
|
|
+ return;
|
||
|
|
+
|
||
|
|
+ if (POINTER_TYPE_P (type1) && POINTER_TYPE_P (type2))
|
||
|
|
+ {
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_two_types_with_uids ("Pointer types: ", type1, type2);
|
||
|
|
+ if (register_ailas_type (type1, type2, ta_map))
|
||
|
|
+ analyze_pointees (type1, type2);
|
||
|
|
+ }
|
||
|
|
+ /* If function and non-function type pointers alias,
|
||
|
|
+ the function type is unsafe. */
|
||
|
|
+ if (FUNCTION_POINTER_TYPE_P (type1) && !FUNCTION_POINTER_TYPE_P (type2))
|
||
|
|
+ unsafe_types->insert (TYPE_UID (type1));
|
||
|
|
+ if (FUNCTION_POINTER_TYPE_P (type2) && !FUNCTION_POINTER_TYPE_P (type1))
|
||
|
|
+ unsafe_types->insert (TYPE_UID (type2));
|
||
|
|
+
|
||
|
|
+ /* Try to figure out with pointers to incomplete types. */
|
||
|
|
+ if (POINTER_TYPE_P (type1) && POINTER_TYPE_P (type2))
|
||
|
|
+ {
|
||
|
|
+ type1 = TYPE_MAIN_VARIANT (type1);
|
||
|
|
+ type2 = TYPE_MAIN_VARIANT (type2);
|
||
|
|
+ tree base1 = TREE_TYPE (type1);
|
||
|
|
+ tree base2 = TREE_TYPE (type2);
|
||
|
|
+ if (RECORD_OR_UNION_TYPE_P (base1) && RECORD_OR_UNION_TYPE_P (base2))
|
||
|
|
+ {
|
||
|
|
+ tree cb1 = TYPE_CANONICAL (base1);
|
||
|
|
+ tree cb2 = TYPE_CANONICAL (base2);
|
||
|
|
+ if (cb1 && !cb2)
|
||
|
|
+ map_canonical_base_to_pointer (type1, type2);
|
||
|
|
+ if (cb2 && !cb1)
|
||
|
|
+ map_canonical_base_to_pointer (type2, type1);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Maybe register non-void/equal type aliases. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+maybe_register_non_void_aliases (tree t1, tree t2)
|
||
|
|
+{
|
||
|
|
+ gcc_assert (t1 && t2);
|
||
|
|
+ if (type_uid_map->count (TYPE_UID (t1)) == 0)
|
||
|
|
+ (*type_uid_map)[TYPE_UID (t1)] = t1;
|
||
|
|
+ if (type_uid_map->count (TYPE_UID (t2)) == 0)
|
||
|
|
+ (*type_uid_map)[TYPE_UID (t2)] = t2;
|
||
|
|
+
|
||
|
|
+ /* Skip equal and void types. */
|
||
|
|
+ if (t1 == t2 || VOID_TYPE_P (t1) || VOID_TYPE_P (t2))
|
||
|
|
+ return;
|
||
|
|
+ maybe_register_aliases (t1, t2);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Detect function type in call stmt. */
|
||
|
|
+
|
||
|
|
+static tree
|
||
|
|
+get_call_fntype (gcall *stmt)
|
||
|
|
+{
|
||
|
|
+ tree fntype = NULL;
|
||
|
|
+ if (gimple_call_fndecl (stmt) && TREE_TYPE (gimple_call_fndecl (stmt)))
|
||
|
|
+ fntype = TREE_TYPE (gimple_call_fndecl (stmt));
|
||
|
|
+ else
|
||
|
|
+ {
|
||
|
|
+ tree call_fn = gimple_call_fn (stmt);
|
||
|
|
+ tree ptype = TREE_TYPE (call_fn);
|
||
|
|
+ gcc_assert (ptype && TREE_TYPE (ptype));
|
||
|
|
+ fntype = TREE_TYPE (ptype);
|
||
|
|
+ }
|
||
|
|
+ gcc_assert (fntype && fntype != void_type_node
|
||
|
|
+ && (TREE_CODE (fntype) == FUNCTION_TYPE
|
||
|
|
+ || TREE_CODE (fntype) == METHOD_TYPE));
|
||
|
|
+ return fntype;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_global_var (tree decl)
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, "Analyze global var: ");
|
||
|
|
+ print_generic_decl (dump_file, decl, TDF_NONE);
|
||
|
|
+ fprintf (dump_file, "\n");
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+collect_block_elt_types (tree tp, std::list<tree> &types, tree block)
|
||
|
|
+{
|
||
|
|
+ tree vt = TREE_TYPE (tp);
|
||
|
|
+ gcc_assert (vt);
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ {
|
||
|
|
+ const char *msg = TREE_CODE (block) == BLOCK ? "VAR's block: " :
|
||
|
|
+ "VAR's ctor: ";
|
||
|
|
+ fprintf (dump_file, msg);
|
||
|
|
+ print_generic_expr (dump_file, tp);
|
||
|
|
+ dump_type_with_uid (" with type ", vt);
|
||
|
|
+ }
|
||
|
|
+ collect_scalar_types (vt, types);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Compare types of initialization block's or constructor's elements and
|
||
|
|
+ fields of the initializer type to find type aliases. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+compare_block_and_init_type (tree block, tree t1)
|
||
|
|
+{
|
||
|
|
+ std::list<tree> tlist1;
|
||
|
|
+ std::list<tree> tlist2;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Init's type list: ");
|
||
|
|
+ collect_scalar_types (t1, tlist1);
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Block's type list: ");
|
||
|
|
+ if (TREE_CODE (block) == CONSTRUCTOR)
|
||
|
|
+ {
|
||
|
|
+ unsigned HOST_WIDE_INT idx;
|
||
|
|
+ tree value;
|
||
|
|
+ FOR_EACH_CONSTRUCTOR_VALUE (CONSTRUCTOR_ELTS (block), idx, value)
|
||
|
|
+ {
|
||
|
|
+ gcc_assert (value);
|
||
|
|
+ collect_block_elt_types (value, tlist2, block);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ else if (TREE_CODE (block) == BLOCK)
|
||
|
|
+ for (tree var = BLOCK_VARS (block); var; var = DECL_CHAIN (var))
|
||
|
|
+ {
|
||
|
|
+ if (TREE_CODE (var) != VAR_DECL)
|
||
|
|
+ continue;
|
||
|
|
+ collect_block_elt_types (var, tlist2, block);
|
||
|
|
+ }
|
||
|
|
+ else
|
||
|
|
+ gcc_unreachable ();
|
||
|
|
+ compare_type_lists (tlist1, tlist2);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Analyze global var to find type aliases comparing types of var and
|
||
|
|
+ initializer elements. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+analyze_global_var (varpool_node *var)
|
||
|
|
+{
|
||
|
|
+ var->get_constructor();
|
||
|
|
+ tree decl = var->decl;
|
||
|
|
+ if (TREE_CODE (decl) == SSA_NAME || !DECL_INITIAL (decl)
|
||
|
|
+ || integer_zerop (DECL_INITIAL (decl)))
|
||
|
|
+ return;
|
||
|
|
+
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_global_var (decl);
|
||
|
|
+ tree var_type = TREE_TYPE (decl);
|
||
|
|
+ tree init_type = TREE_TYPE (DECL_INITIAL (decl));
|
||
|
|
+ gcc_assert (var_type && init_type);
|
||
|
|
+ if (RECORD_OR_UNION_TYPE_P (init_type)
|
||
|
|
+ && !initializer_zerop (DECL_INITIAL (decl)))
|
||
|
|
+ compare_block_and_init_type (DECL_INITIAL (decl), init_type);
|
||
|
|
+ else if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Is not a record with nonzero init\n");
|
||
|
|
+
|
||
|
|
+ if (var_type == init_type)
|
||
|
|
+ return;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_two_types_with_uids ("Mismatch of var and init types: ",
|
||
|
|
+ var_type, init_type);
|
||
|
|
+ collect_scalar_types_and_find_aliases (var_type, init_type);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_function_node_info (struct cgraph_node *n)
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, "\nAnalyse function node: ");
|
||
|
|
+ print_generic_expr (dump_file, n->decl);
|
||
|
|
+ fprintf (dump_file, "\n");
|
||
|
|
+ tree fndecl_type = TREE_TYPE (n->decl);
|
||
|
|
+ dump_type_with_uid ("Function decl type: ", fndecl_type, TDF_UID);
|
||
|
|
+ if (TREE_TYPE (fndecl_type))
|
||
|
|
+ dump_type_with_uid ("Return type: ", TREE_TYPE (fndecl_type));
|
||
|
|
+ tree argt = TYPE_ARG_TYPES (fndecl_type);
|
||
|
|
+ for (unsigned i = 1; argt && argt != void_type_node
|
||
|
|
+ && !VOID_TYPE_P (TREE_VALUE (argt)); ++i, argt = TREE_CHAIN (argt))
|
||
|
|
+ {
|
||
|
|
+ tree atype = TREE_VALUE (argt);
|
||
|
|
+ fprintf (dump_file, "%d-arg type: ", i);
|
||
|
|
+ dump_type_with_uid ("", atype);
|
||
|
|
+ }
|
||
|
|
+ fprintf (dump_file, "\n");
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_call_stmt_info (gcall *stmt, tree fntype)
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, "\nAnalyse call stmt: ");
|
||
|
|
+ if (stmt)
|
||
|
|
+ print_gimple_stmt (dump_file, stmt, 3, TDF_DETAILS);
|
||
|
|
+ else
|
||
|
|
+ fprintf (dump_file, "(no stmt)\n");
|
||
|
|
+ dump_type_with_uid ("fntype=", fntype, TDF_UID);
|
||
|
|
+ if (gimple_call_fntype (stmt))
|
||
|
|
+ dump_type_with_uid ("fntype1=", gimple_call_fntype (stmt), TDF_UID);
|
||
|
|
+ if (gimple_call_fndecl (stmt) && TREE_TYPE (gimple_call_fndecl (stmt)))
|
||
|
|
+ dump_type_with_uid ("fntype2=", TREE_TYPE (gimple_call_fndecl (stmt)),
|
||
|
|
+ TDF_UID);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Dump actual and formal arg types. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_arg_types_with_uids (int i, tree t1, tree t2)
|
||
|
|
+{
|
||
|
|
+ if (i >= 0)
|
||
|
|
+ fprintf (dump_file, "Call's %d-arg types: ", i);
|
||
|
|
+ else
|
||
|
|
+ fprintf (dump_file, "Call's return types: ");
|
||
|
|
+ fprintf (dump_file, "(%d) and (%d) ", TYPE_UID (t1), TYPE_UID (t2));
|
||
|
|
+ print_generic_expr (dump_file, t1, TDF_UID);
|
||
|
|
+ fprintf (dump_file, " ");
|
||
|
|
+ print_generic_expr (dump_file, t2, TDF_UID);
|
||
|
|
+ fprintf (dump_file, "\n");
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Analyze call graph edge with connected call stmt to find type aliases in
|
||
|
|
+ arguments and return value casts. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+analyze_cgraph_edge (cgraph_edge *e)
|
||
|
|
+{
|
||
|
|
+ gcall *stmt = e->call_stmt;
|
||
|
|
+ gcc_assert (stmt != NULL);
|
||
|
|
+ tree fntype = get_call_fntype (stmt);
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_call_stmt_info (stmt, fntype);
|
||
|
|
+ if (gimple_has_lhs (stmt))
|
||
|
|
+ {
|
||
|
|
+ tree t1 = TREE_TYPE (gimple_call_lhs (stmt));
|
||
|
|
+ tree t2 = TREE_TYPE (fntype);
|
||
|
|
+ const int is_return_arg = -1;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_arg_types_with_uids (is_return_arg, t1, t2);
|
||
|
|
+ maybe_register_non_void_aliases (t1, t2);
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ tree argt = TYPE_ARG_TYPES (fntype);
|
||
|
|
+ if (!argt)
|
||
|
|
+ {
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Finish call stmt analysis\n");
|
||
|
|
+ return;
|
||
|
|
+ }
|
||
|
|
+ gcc_assert (argt);
|
||
|
|
+ unsigned num_args = gimple_call_num_args (stmt);
|
||
|
|
+ for (unsigned i = 0; i < num_args && argt; ++i, argt = TREE_CHAIN (argt))
|
||
|
|
+ {
|
||
|
|
+ tree arg = gimple_call_arg (stmt, i);
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_arg_types_with_uids (i, TREE_VALUE (argt), TREE_TYPE (arg));
|
||
|
|
+ if (TREE_VALUE (argt) == TREE_TYPE (arg)
|
||
|
|
+ || !POINTER_TYPE_P (TREE_VALUE (argt))
|
||
|
|
+ || !POINTER_TYPE_P (TREE_TYPE (arg)))
|
||
|
|
+ continue;
|
||
|
|
+ maybe_register_non_void_aliases (TREE_VALUE (argt), TREE_TYPE (arg));
|
||
|
|
+ tree t1 = TREE_TYPE (TREE_VALUE (argt));
|
||
|
|
+ tree t2 = TREE_TYPE (TREE_TYPE (arg));
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Call's %d-arg base types: (%d) and (%d)\n",
|
||
|
|
+ i, (t1 ? TYPE_UID (t1) : 0), (t2 ? TYPE_UID (t2) : 0));
|
||
|
|
+ maybe_register_non_void_aliases (t1, t2);
|
||
|
|
+ }
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "End list of args\n");
|
||
|
|
+ tree fndecl_type = NULL;
|
||
|
|
+ if (e->callee && e->callee->decl)
|
||
|
|
+ fndecl_type = TREE_TYPE (e->callee->decl);
|
||
|
|
+ if (fndecl_type && fndecl_type != fntype)
|
||
|
|
+ {
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Function decl and edge types mismatch:\n");
|
||
|
|
+ register_ailas_type (fntype, fndecl_type, fta_map);
|
||
|
|
+ }
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "End call stmt analysis\n");
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_assign_info (gimple *stmt, tree rhs, tree lhs_type, tree rhs_type)
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, "\nAnalyse assign cast/copy stmt, rhs=%s: ",
|
||
|
|
+ get_tree_code_name (TREE_CODE (rhs)));
|
||
|
|
+ print_gimple_stmt (dump_file, stmt, 3, TDF_DETAILS);
|
||
|
|
+ fprintf (dump_file, "Types: ");
|
||
|
|
+ print_generic_expr (dump_file, lhs_type);
|
||
|
|
+ fprintf (dump_file, ", ");
|
||
|
|
+ print_generic_expr (dump_file, rhs_type);
|
||
|
|
+ fprintf (dump_file, "\n");
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Analyze cast/copy assign stmt to find type aliases. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+analyze_assign_stmt (gimple *stmt)
|
||
|
|
+{
|
||
|
|
+ gcc_assert (is_gimple_assign (stmt));
|
||
|
|
+ tree rhs_type = NULL_TREE;
|
||
|
|
+ tree lhs_type = TREE_TYPE (gimple_assign_lhs (stmt));
|
||
|
|
+ tree rhs = gimple_assign_rhs1 (stmt);
|
||
|
|
+ if (TREE_CODE (rhs) == MEM_REF)
|
||
|
|
+ {
|
||
|
|
+ rhs = TREE_OPERAND (rhs, 0);
|
||
|
|
+ tree ptr_type = TREE_TYPE (rhs);
|
||
|
|
+ gcc_assert (POINTER_TYPE_P (ptr_type));
|
||
|
|
+ rhs_type = TREE_TYPE (ptr_type);
|
||
|
|
+ }
|
||
|
|
+ else if (TREE_CODE (rhs) == ADDR_EXPR)
|
||
|
|
+ {
|
||
|
|
+ rhs = TREE_OPERAND (rhs, 0);
|
||
|
|
+ if (VAR_OR_FUNCTION_DECL_P (rhs) || TREE_CODE (rhs) == STRING_CST
|
||
|
|
+ || TREE_CODE (rhs) == ARRAY_REF || TREE_CODE (rhs) == PARM_DECL)
|
||
|
|
+ rhs_type = build_pointer_type (TREE_TYPE (rhs));
|
||
|
|
+ else if (TREE_CODE (rhs) == COMPONENT_REF)
|
||
|
|
+ {
|
||
|
|
+ rhs = TREE_OPERAND (rhs, 1);
|
||
|
|
+ rhs_type = build_pointer_type (TREE_TYPE (rhs));
|
||
|
|
+ }
|
||
|
|
+ else if (TREE_CODE (rhs) == MEM_REF)
|
||
|
|
+ {
|
||
|
|
+ rhs = TREE_OPERAND (rhs, 0);
|
||
|
|
+ rhs_type = TREE_TYPE (rhs);
|
||
|
|
+ gcc_assert (POINTER_TYPE_P (rhs_type));
|
||
|
|
+ }
|
||
|
|
+ else
|
||
|
|
+ gcc_unreachable();
|
||
|
|
+ }
|
||
|
|
+ else
|
||
|
|
+ rhs_type = TREE_TYPE (rhs);
|
||
|
|
+
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_assign_info (stmt, rhs, lhs_type, rhs_type);
|
||
|
|
+ if (CONSTANT_CLASS_P (rhs) && !zerop (rhs)
|
||
|
|
+ && FUNCTION_POINTER_TYPE_P (TREE_TYPE (rhs)))
|
||
|
|
+ {
|
||
|
|
+ tree ftype = TREE_TYPE (rhs_type);
|
||
|
|
+ unsafe_types->insert (TYPE_UID (ftype));
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Function type (%d) is unsafe due to assign "
|
||
|
|
+ "non-zero cst to function pointer\n", TYPE_UID (ftype));
|
||
|
|
+ }
|
||
|
|
+ maybe_register_non_void_aliases (lhs_type, rhs_type);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Walk all fn's stmt to analyze assigns. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+analyze_assigns (function* fn)
|
||
|
|
+{
|
||
|
|
+ push_cfun (fn);
|
||
|
|
+ basic_block bb;
|
||
|
|
+ gimple_stmt_iterator si;
|
||
|
|
+ FOR_EACH_BB_FN (bb, fn)
|
||
|
|
+ for (si = gsi_start_bb (bb); !gsi_end_p (si); gsi_next (&si))
|
||
|
|
+ {
|
||
|
|
+ gimple *stmt = gsi_stmt (si);
|
||
|
|
+ if (!gimple_assign_cast_p (stmt) && !gimple_assign_copy_p (stmt))
|
||
|
|
+ continue;
|
||
|
|
+ analyze_assign_stmt (stmt);
|
||
|
|
+ }
|
||
|
|
+ pop_cfun ();
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Walk all functions to collect sets of type aliases. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+collect_type_alias_sets ()
|
||
|
|
+{
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "\n\nCollect type alias sets walking global vars.\n");
|
||
|
|
+
|
||
|
|
+ varpool_node *var;
|
||
|
|
+ FOR_EACH_VARIABLE (var)
|
||
|
|
+ if (var->real_symbol_p ())
|
||
|
|
+ analyze_global_var (var);
|
||
|
|
+
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "\nCollect type alias sets walking functions.\n");
|
||
|
|
+
|
||
|
|
+ struct cgraph_node *n;
|
||
|
|
+ FOR_EACH_FUNCTION (n)
|
||
|
|
+ {
|
||
|
|
+ if (!n->has_gimple_body_p ())
|
||
|
|
+ continue;
|
||
|
|
+ n->get_body ();
|
||
|
|
+ function *fn = DECL_STRUCT_FUNCTION (n->decl);
|
||
|
|
+ if (!fn)
|
||
|
|
+ continue;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_function_node_info (n);
|
||
|
|
+ /* Analyze direct/indirect function calls. */
|
||
|
|
+ for (cgraph_edge *e = n->callees; e; e = e->next_callee)
|
||
|
|
+ analyze_cgraph_edge (e);
|
||
|
|
+ for (cgraph_edge *e = n->indirect_calls; e; e = e->next_callee)
|
||
|
|
+ analyze_cgraph_edge (e);
|
||
|
|
+ /* Analyze assign (with casts) statements. */
|
||
|
|
+ analyze_assigns (fn);
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+process_cbase_to_ptype_map ()
|
||
|
|
+{
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "\nProcess types in cbase-to-ptypes map:\n");
|
||
|
|
+
|
||
|
|
+ for (type_alias_map::iterator it1 = cbase_to_ptype->begin ();
|
||
|
|
+ it1 != cbase_to_ptype->end (); ++it1)
|
||
|
|
+ {
|
||
|
|
+ type_set *set = it1->second;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_type_uid_with_set ("cb=(%d): ", (*type_uid_map)[it1->first],
|
||
|
|
+ cbase_to_ptype);
|
||
|
|
+ tree ctype = NULL;
|
||
|
|
+ for (type_set::const_iterator it2 = set->begin ();
|
||
|
|
+ it2 != set->end (); it2++)
|
||
|
|
+ {
|
||
|
|
+ tree t2 = (*type_uid_map)[*it2];
|
||
|
|
+ if (t2 == TYPE_MAIN_VARIANT (t2))
|
||
|
|
+ {
|
||
|
|
+ ctype = t2;
|
||
|
|
+ break;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ if (!ctype)
|
||
|
|
+ continue;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_type_with_uid ("Select canonical type: ", ctype);
|
||
|
|
+ for (type_set::const_iterator it2 = set->begin ();
|
||
|
|
+ it2 != set->end (); it2++)
|
||
|
|
+ {
|
||
|
|
+ tree t = (*type_uid_map)[*it2];
|
||
|
|
+ if (!ctype_map->count (t))
|
||
|
|
+ {
|
||
|
|
+ (*ctype_map)[t] = ctype;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Set canonical type for (%d)->c(%d)\n",
|
||
|
|
+ *it2, TYPE_UID (ctype));
|
||
|
|
+ }
|
||
|
|
+ else if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Canonical type is already set (%d)->c(%d)\n",
|
||
|
|
+ *it2, TYPE_UID ((*ctype_map)[t]));
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+set_canonical_type_for_type_set (type_set *set)
|
||
|
|
+{
|
||
|
|
+ tree one_canonical = NULL;
|
||
|
|
+ for (type_set::const_iterator it = set->begin (); it != set->end (); it++)
|
||
|
|
+ {
|
||
|
|
+ tree t = (*type_uid_map)[*it];
|
||
|
|
+ gcc_assert (t);
|
||
|
|
+ if ((TYPE_CANONICAL (t) || ctype_map->count (t)))
|
||
|
|
+ {
|
||
|
|
+ one_canonical = TYPE_CANONICAL (t) ? TYPE_CANONICAL (t)
|
||
|
|
+ : (*ctype_map)[t];
|
||
|
|
+ gcc_assert (COMPLETE_TYPE_P (t));
|
||
|
|
+ break;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ for (type_set::const_iterator it = set->begin (); it != set->end (); it++)
|
||
|
|
+ {
|
||
|
|
+ tree t = (*type_uid_map)[*it];
|
||
|
|
+ if (!ctype_map->count (t))
|
||
|
|
+ {
|
||
|
|
+ (*ctype_map)[t] = one_canonical;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ {
|
||
|
|
+ if (one_canonical)
|
||
|
|
+ fprintf (dump_file, "Set canonical type for (%d)->c(%d)\n",
|
||
|
|
+ TYPE_UID (t), TYPE_UID (one_canonical));
|
||
|
|
+ else
|
||
|
|
+ fprintf (dump_file, "Set NULL canonical for (%d)\n", *it);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ else if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ {
|
||
|
|
+ tree ct = (*ctype_map)[t];
|
||
|
|
+ fprintf (dump_file, "Canonical type is already set (%d)->c(%d)\n",
|
||
|
|
+ TYPE_UID (t), ct ? TYPE_UID (ct) : -1);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_is_type_set_incomplete (type_set * set)
|
||
|
|
+{
|
||
|
|
+ bool has_complete_types = false;
|
||
|
|
+ for (type_set::const_iterator it = set->begin (); it != set->end (); it++)
|
||
|
|
+ if (COMPLETE_TYPE_P ((*type_uid_map)[*it]))
|
||
|
|
+ {
|
||
|
|
+ has_complete_types = true;
|
||
|
|
+ break;
|
||
|
|
+ }
|
||
|
|
+ if (!has_complete_types)
|
||
|
|
+ fprintf (dump_file, "Set of incomplete types\n");
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+process_alias_type_sets ()
|
||
|
|
+{
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "\nProcess alias sets of types:\n");
|
||
|
|
+ /* Keep processed types to process each type set (in ta_map) only once. */
|
||
|
|
+ type_set processed_types;
|
||
|
|
+ for (type_alias_map::iterator it1 = ta_map->begin ();
|
||
|
|
+ it1 != ta_map->end (); ++it1)
|
||
|
|
+ {
|
||
|
|
+ tree type = (*type_uid_map)[it1->first];
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_type_uid_with_set ("(%d) ", type, ta_map);
|
||
|
|
+ if (processed_types.count (TYPE_UID (type)) != 0
|
||
|
|
+ || unsafe_types->count (TYPE_UID (type)) != 0)
|
||
|
|
+ continue;
|
||
|
|
+ type_set *set = it1->second;
|
||
|
|
+ for (type_set::const_iterator it2 = set->begin ();
|
||
|
|
+ it2 != set->end (); it2++)
|
||
|
|
+ processed_types.insert (*it2);
|
||
|
|
+ /* Check if this type set contains function pointers and
|
||
|
|
+ non-function pointers. */
|
||
|
|
+ bool has_no_fp = false, has_fp = false;
|
||
|
|
+ for (type_set::const_iterator it2 = set->begin ();
|
||
|
|
+ it2 != set->end (); it2++)
|
||
|
|
+ {
|
||
|
|
+ tree t2 = (*type_uid_map)[*it2];
|
||
|
|
+ if (FUNCTION_POINTER_TYPE_P (t2))
|
||
|
|
+ has_fp = true;
|
||
|
|
+ else
|
||
|
|
+ has_no_fp = true;
|
||
|
|
+ if (has_fp && has_no_fp)
|
||
|
|
+ break;
|
||
|
|
+ }
|
||
|
|
+ if (has_fp)
|
||
|
|
+ {
|
||
|
|
+ for (type_set::const_iterator it2 = set->begin ();
|
||
|
|
+ it2 != set->end (); it2++)
|
||
|
|
+ {
|
||
|
|
+ tree t2 = (*type_uid_map)[*it2];
|
||
|
|
+ /* If it's a type set with mixed function and not-function types,
|
||
|
|
+ mark all function pointer types in the set as unsafe. */
|
||
|
|
+ if (has_no_fp && FUNCTION_POINTER_TYPE_P (t2))
|
||
|
|
+ {
|
||
|
|
+ tree ftype = TREE_TYPE (t2);
|
||
|
|
+ unsafe_types->insert (TYPE_UID (ftype));
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Insert function type (%d) to unsafe "
|
||
|
|
+ "due to escape its pointer type (%d) to mixed "
|
||
|
|
+ "alias set (printed before)\n",
|
||
|
|
+ TYPE_UID (ftype), TYPE_UID (t2));
|
||
|
|
+ }
|
||
|
|
+ /* If it's a type set with only function pointer types,
|
||
|
|
+ mark all base function types in the set as aliases. */
|
||
|
|
+ if (!has_no_fp)
|
||
|
|
+ {
|
||
|
|
+ gcc_assert (FUNCTION_POINTER_TYPE_P (type)
|
||
|
|
+ && FUNCTION_POINTER_TYPE_P (t2));
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Insert function type aliases by "
|
||
|
|
+ "function pointer aliases:\n");
|
||
|
|
+ register_ailas_type (TREE_TYPE (type), TREE_TYPE (t2),
|
||
|
|
+ fta_map);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ set_canonical_type_for_type_set (set);
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_is_type_set_incomplete (set);
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_unsafe_and_canonical_types ()
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, "\nList of unsafe types:\n");
|
||
|
|
+ for (type_set::iterator it = unsafe_types->begin ();
|
||
|
|
+ it != unsafe_types->end (); ++it)
|
||
|
|
+ {
|
||
|
|
+ print_generic_expr (dump_file, (*type_uid_map)[*it]);
|
||
|
|
+ fprintf (dump_file, " (%d)\n", *it);
|
||
|
|
+ }
|
||
|
|
+ fprintf (dump_file, "\nList of alias canonical types:\n");
|
||
|
|
+ for (type_alias_map::iterator it = ta_map->begin ();
|
||
|
|
+ it != ta_map->end (); ++it)
|
||
|
|
+ {
|
||
|
|
+ tree type = (*type_uid_map)[it->first];
|
||
|
|
+ if (ctype_map->count (type) == 0)
|
||
|
|
+ continue;
|
||
|
|
+ print_generic_expr (dump_file, type);
|
||
|
|
+ fprintf (dump_file, " -> ");
|
||
|
|
+ tree ctype = (*ctype_map)[type];
|
||
|
|
+ if (ctype != NULL)
|
||
|
|
+ {
|
||
|
|
+ print_generic_expr (dump_file, ctype);
|
||
|
|
+ fprintf (dump_file, " (%d)->(%d)\n",
|
||
|
|
+ TYPE_UID (type), TYPE_UID (ctype));
|
||
|
|
+ }
|
||
|
|
+ else
|
||
|
|
+ fprintf (dump_file, " null\n");
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+init_function_type_alias_for_edge (cgraph_edge *e)
|
||
|
|
+{
|
||
|
|
+ gcall *stmt = e->call_stmt;
|
||
|
|
+ gcc_assert (stmt != NULL);
|
||
|
|
+ tree fntype = get_call_fntype (stmt);
|
||
|
|
+ if (fta_map->count (TYPE_UID (fntype)) == 0)
|
||
|
|
+ register_ailas_type (fntype, fntype, fta_map);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* This pass over all function types makes each function type to have
|
||
|
|
+ at least one alias (itself). */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+init_function_type_aliases ()
|
||
|
|
+{
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "\nInit aliases for all function types.\n");
|
||
|
|
+
|
||
|
|
+ struct cgraph_node *n;
|
||
|
|
+ FOR_EACH_FUNCTION (n)
|
||
|
|
+ {
|
||
|
|
+ tree fntype = TREE_TYPE (n->decl);
|
||
|
|
+ if (fta_map->count (TYPE_UID (fntype)) == 0)
|
||
|
|
+ register_ailas_type (fntype, fntype, fta_map);
|
||
|
|
+
|
||
|
|
+ if (!n->has_gimple_body_p ())
|
||
|
|
+ continue;
|
||
|
|
+ n->get_body ();
|
||
|
|
+ function *fn = DECL_STRUCT_FUNCTION (n->decl);
|
||
|
|
+ if (!fn)
|
||
|
|
+ continue;
|
||
|
|
+
|
||
|
|
+ /* Init for function types of direct/indirect callees. */
|
||
|
|
+ for (cgraph_edge *e = n->callees; e; e = e->next_callee)
|
||
|
|
+ init_function_type_alias_for_edge (e);
|
||
|
|
+ for (cgraph_edge *e = n->indirect_calls; e; e = e->next_callee)
|
||
|
|
+ init_function_type_alias_for_edge (e);
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* In lto-common.c there is the global canonical type table and the
|
||
|
|
+ corresponding machinery which detects the same types from differens
|
||
|
|
+ modules and joins them assigning the one canonical type. However
|
||
|
|
+ lto does not set the goal to do a complete and precise matching, so
|
||
|
|
+ sometimes a few types has no TYPE_CANONICAL set. Since ICP relies on
|
||
|
|
+ precise type matching, we create the similar table and register all
|
||
|
|
+ the required types in it. */
|
||
|
|
+
|
||
|
|
+static std::map<const_tree, hashval_t> *canonical_type_hash_cache = NULL;
|
||
|
|
+static std::map<hashval_t, tree> *icp_canonical_types = NULL;
|
||
|
|
+
|
||
|
|
+static hashval_t hash_canonical_type (tree type);
|
||
|
|
+
|
||
|
|
+/* Register canonical type in icp_canonical_types and ctype_map evaluating
|
||
|
|
+ its hash (using hash_canonical_type) if it's needed. */
|
||
|
|
+
|
||
|
|
+static hashval_t
|
||
|
|
+icp_register_canonical_type (tree t)
|
||
|
|
+{
|
||
|
|
+ hashval_t hash;
|
||
|
|
+ if (canonical_type_hash_cache->count ((const_tree) t) == 0)
|
||
|
|
+ {
|
||
|
|
+ tree t1 = TYPE_MAIN_VARIANT (t);
|
||
|
|
+ if (!COMPLETE_TYPE_P (t1) && TYPE_CANONICAL (t1)
|
||
|
|
+ && COMPLETE_TYPE_P (TYPE_CANONICAL (t1)))
|
||
|
|
+ {
|
||
|
|
+ t1 = TYPE_CANONICAL (t1);
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Use complete canonical (%d) for (%d)\n",
|
||
|
|
+ TYPE_UID (t1), TYPE_UID (t));
|
||
|
|
+ }
|
||
|
|
+ hash = hash_canonical_type (t1);
|
||
|
|
+ /* Cache the just computed hash value. */
|
||
|
|
+ (*canonical_type_hash_cache)[(const_tree) t] = hash;
|
||
|
|
+ }
|
||
|
|
+ else
|
||
|
|
+ hash = (*canonical_type_hash_cache)[(const_tree) t];
|
||
|
|
+
|
||
|
|
+ tree new_type = t;
|
||
|
|
+ if (icp_canonical_types->count (hash))
|
||
|
|
+ {
|
||
|
|
+ new_type = (*icp_canonical_types)[hash];
|
||
|
|
+ gcc_checking_assert (new_type != t);
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Found canonical (%d) for (%d), h=%u\n",
|
||
|
|
+ TYPE_UID (new_type), TYPE_UID (t), (unsigned int) hash);
|
||
|
|
+ }
|
||
|
|
+ else
|
||
|
|
+ {
|
||
|
|
+ (*icp_canonical_types)[hash] = t;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Register canonical %d, h=%u\n", TYPE_UID (t),
|
||
|
|
+ (unsigned int) hash);
|
||
|
|
+ }
|
||
|
|
+ if (ctype_map->count (t) == 0)
|
||
|
|
+ (*ctype_map)[t] = new_type;
|
||
|
|
+ return hash;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Merge hstate with hash of the given type. If the type is not registered,
|
||
|
|
+ register it in the maps of the canonical types. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+iterative_hash_canonical_type (tree type, inchash::hash &hstate)
|
||
|
|
+{
|
||
|
|
+ hashval_t v;
|
||
|
|
+ /* All type variants have same TYPE_CANONICAL. */
|
||
|
|
+ type = TYPE_MAIN_VARIANT (type);
|
||
|
|
+ if (canonical_type_hash_cache->count ((const_tree) type))
|
||
|
|
+ v = (*canonical_type_hash_cache)[(const_tree) type];
|
||
|
|
+ else
|
||
|
|
+ v = icp_register_canonical_type (type);
|
||
|
|
+ hstate.merge_hash (v);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Compute and return hash for the given type. It does not take into account
|
||
|
|
+ base types of pointer types. */
|
||
|
|
+
|
||
|
|
+static hashval_t
|
||
|
|
+hash_canonical_type (tree type)
|
||
|
|
+{
|
||
|
|
+ inchash::hash hstate;
|
||
|
|
+ enum tree_code code;
|
||
|
|
+ /* Combine a few common features of types so that types are grouped into
|
||
|
|
+ smaller sets; when searching for existing matching types to merge,
|
||
|
|
+ only existing types having the same features as the new type will be
|
||
|
|
+ checked. */
|
||
|
|
+ code = tree_code_for_canonical_type_merging (TREE_CODE (type));
|
||
|
|
+ hstate.add_int (code);
|
||
|
|
+ if (!RECORD_OR_UNION_TYPE_P (type))
|
||
|
|
+ hstate.add_int (TYPE_MODE (type));
|
||
|
|
+ /* Incorporate common features of numerical types. */
|
||
|
|
+ if (INTEGRAL_TYPE_P (type)
|
||
|
|
+ || SCALAR_FLOAT_TYPE_P (type)
|
||
|
|
+ || FIXED_POINT_TYPE_P (type)
|
||
|
|
+ || TREE_CODE (type) == OFFSET_TYPE
|
||
|
|
+ || POINTER_TYPE_P (type))
|
||
|
|
+ {
|
||
|
|
+ hstate.add_int (TYPE_PRECISION (type));
|
||
|
|
+ if (!type_with_interoperable_signedness (type))
|
||
|
|
+ hstate.add_int (TYPE_UNSIGNED (type));
|
||
|
|
+ }
|
||
|
|
+ if (VECTOR_TYPE_P (type))
|
||
|
|
+ {
|
||
|
|
+ hstate.add_poly_int (TYPE_VECTOR_SUBPARTS (type));
|
||
|
|
+ hstate.add_int (TYPE_UNSIGNED (type));
|
||
|
|
+ }
|
||
|
|
+ if (TREE_CODE (type) == COMPLEX_TYPE)
|
||
|
|
+ hstate.add_int (TYPE_UNSIGNED (type));
|
||
|
|
+ if (POINTER_TYPE_P (type))
|
||
|
|
+ hstate.add_int (TYPE_ADDR_SPACE (TREE_TYPE (type)));
|
||
|
|
+ /* For array types hash the domain bounds and the string flag. */
|
||
|
|
+ if (TREE_CODE (type) == ARRAY_TYPE && TYPE_DOMAIN (type))
|
||
|
|
+ {
|
||
|
|
+ hstate.add_int (TYPE_STRING_FLAG (type));
|
||
|
|
+ /* OMP lowering can introduce error_mark_node in place of
|
||
|
|
+ random local decls in types. */
|
||
|
|
+ if (TYPE_MIN_VALUE (TYPE_DOMAIN (type)) != error_mark_node)
|
||
|
|
+ inchash::add_expr (TYPE_MIN_VALUE (TYPE_DOMAIN (type)), hstate);
|
||
|
|
+ if (TYPE_MAX_VALUE (TYPE_DOMAIN (type)) != error_mark_node)
|
||
|
|
+ inchash::add_expr (TYPE_MAX_VALUE (TYPE_DOMAIN (type)), hstate);
|
||
|
|
+ }
|
||
|
|
+ /* Recurse for aggregates with a single element type. */
|
||
|
|
+ if (TREE_CODE (type) == ARRAY_TYPE
|
||
|
|
+ || TREE_CODE (type) == COMPLEX_TYPE
|
||
|
|
+ || TREE_CODE (type) == VECTOR_TYPE)
|
||
|
|
+ iterative_hash_canonical_type (TREE_TYPE (type), hstate);
|
||
|
|
+ /* Incorporate function return and argument types. */
|
||
|
|
+ if (TREE_CODE (type) == FUNCTION_TYPE || TREE_CODE (type) == METHOD_TYPE)
|
||
|
|
+ {
|
||
|
|
+ unsigned nargs = 0;
|
||
|
|
+ iterative_hash_canonical_type (TREE_TYPE (type), hstate);
|
||
|
|
+ for (tree p = TYPE_ARG_TYPES (type); p; p = TREE_CHAIN (p))
|
||
|
|
+ {
|
||
|
|
+ iterative_hash_canonical_type (TREE_VALUE (p), hstate);
|
||
|
|
+ nargs++;
|
||
|
|
+ }
|
||
|
|
+ hstate.add_int (nargs);
|
||
|
|
+ }
|
||
|
|
+ if (RECORD_OR_UNION_TYPE_P (type))
|
||
|
|
+ {
|
||
|
|
+ unsigned nfields = 0;
|
||
|
|
+ for (tree f = TYPE_FIELDS (type); f; f = TREE_CHAIN (f))
|
||
|
|
+ if (TREE_CODE (f) == FIELD_DECL)
|
||
|
|
+ {
|
||
|
|
+ iterative_hash_canonical_type (TREE_TYPE (f), hstate);
|
||
|
|
+ nfields++;
|
||
|
|
+ }
|
||
|
|
+ hstate.add_int (nfields);
|
||
|
|
+ }
|
||
|
|
+ return hstate.end ();
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* It finds canonical type in ctype_map and icp_canonical_types maps. */
|
||
|
|
+
|
||
|
|
+static tree
|
||
|
|
+find_canonical_type (tree type)
|
||
|
|
+{
|
||
|
|
+ if (ctype_map->count (type))
|
||
|
|
+ return (*ctype_map)[type];
|
||
|
|
+ if (canonical_type_hash_cache->count ((const_tree) type) == 0)
|
||
|
|
+ return NULL;
|
||
|
|
+ hashval_t h = (*canonical_type_hash_cache)[(const_tree) type];
|
||
|
|
+ if (icp_canonical_types->count (h))
|
||
|
|
+ return (*icp_canonical_types)[h];
|
||
|
|
+ return NULL;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* It updates hash for the given type taking into account pointees in pointer
|
||
|
|
+ types. If the type is incomplete function type, it returns true. It's used
|
||
|
|
+ only for function type hash calculation. */
|
||
|
|
+
|
||
|
|
+static bool
|
||
|
|
+initial_hash_canonical_type (tree type, inchash::hash &hstate)
|
||
|
|
+{
|
||
|
|
+ /* All type variants have same TYPE_CANONICAL. */
|
||
|
|
+ type = TYPE_MAIN_VARIANT (type);
|
||
|
|
+ if (VOID_TYPE_P (type))
|
||
|
|
+ {
|
||
|
|
+ hstate.add_int (POINTER_TYPE);
|
||
|
|
+ return false;
|
||
|
|
+ }
|
||
|
|
+ hstate.add_int (TREE_CODE (type));
|
||
|
|
+ hstate.add_int (TYPE_MODE (type));
|
||
|
|
+ if (POINTER_TYPE_P (type))
|
||
|
|
+ {
|
||
|
|
+ tree base_type = TREE_TYPE (type);
|
||
|
|
+ hstate.add_int (TYPE_ADDR_SPACE (base_type));
|
||
|
|
+ return initial_hash_canonical_type (base_type, hstate);
|
||
|
|
+ }
|
||
|
|
+ tree ctype = find_canonical_type (type);
|
||
|
|
+ if (!ctype)
|
||
|
|
+ {
|
||
|
|
+ if (TREE_CODE (type) == FUNCTION_TYPE || TREE_CODE (type) == METHOD_TYPE)
|
||
|
|
+ {
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Due to ftype (%d)\n", TYPE_UID (type));
|
||
|
|
+ return true;
|
||
|
|
+ }
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_type_with_uid ("Has NO canonical type: ", type, TDF_UID);
|
||
|
|
+ icp_register_canonical_type (type);
|
||
|
|
+ if (ctype_map->count(type))
|
||
|
|
+ ctype = (*ctype_map)[type];
|
||
|
|
+ if (ctype && dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_type_with_uid ("Found canonical type: ", ctype, TDF_UID);
|
||
|
|
+ }
|
||
|
|
+ else if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_type_with_uid ("Canonical type: ", ctype, TDF_UID);
|
||
|
|
+ hstate.add_int (TYPE_UID (ctype));
|
||
|
|
+ return false;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* It returns hash value for the given function type. If the function type is
|
||
|
|
+ incomplete, insert it in the incomplete_hash_ftype set. */
|
||
|
|
+
|
||
|
|
+static hashval_t
|
||
|
|
+get_hash_for_ftype (tree type, type_set *incomplete_hash_ftype)
|
||
|
|
+{
|
||
|
|
+ bool incomplete = false;
|
||
|
|
+ inchash::hash hstate;
|
||
|
|
+ /* Function type is expected. */
|
||
|
|
+ gcc_assert (TREE_CODE (type) == FUNCTION_TYPE
|
||
|
|
+ || TREE_CODE (type) == METHOD_TYPE);
|
||
|
|
+ /* Hash return type. */
|
||
|
|
+ tree rt = TREE_TYPE (type);
|
||
|
|
+ tree ct = rt ? find_canonical_type (rt) : void_type_node;
|
||
|
|
+ incomplete |= initial_hash_canonical_type (ct ? ct : rt, hstate);
|
||
|
|
+ /* Hash arg types. */
|
||
|
|
+ tree argt = TYPE_ARG_TYPES (type);
|
||
|
|
+ if (!argt)
|
||
|
|
+ incomplete |= initial_hash_canonical_type (void_type_node, hstate);
|
||
|
|
+ else
|
||
|
|
+ for (unsigned i = 1; argt; ++i, argt = TREE_CHAIN (argt))
|
||
|
|
+ {
|
||
|
|
+ tree ct = find_canonical_type (TREE_VALUE (argt));
|
||
|
|
+ ct = ct ? ct : TREE_VALUE (argt);
|
||
|
|
+ incomplete |= initial_hash_canonical_type (ct, hstate);
|
||
|
|
+ }
|
||
|
|
+ if (incomplete && incomplete_hash_ftype->count (TYPE_UID (type)) == 0)
|
||
|
|
+ incomplete_hash_ftype->insert (TYPE_UID (type));
|
||
|
|
+ else if (!incomplete && incomplete_hash_ftype->count (TYPE_UID (type)) != 0)
|
||
|
|
+ incomplete_hash_ftype->erase (TYPE_UID (type));
|
||
|
|
+ return hstate.end();
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Find type aliases evaluating type hashes and connecting types with
|
||
|
|
+ the same hash values. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+find_type_aliases_by_compatibility ()
|
||
|
|
+{
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "\nFind type aliases checking their compatibility.\n");
|
||
|
|
+
|
||
|
|
+ std::map<hashval_t, tree> hash_to_ftype;
|
||
|
|
+ type_set *incomplete_hash_ftype = new type_set;
|
||
|
|
+ canonical_type_hash_cache = new std::map<const_tree, hashval_t>;
|
||
|
|
+ icp_canonical_types = new std::map<hashval_t, tree>;
|
||
|
|
+
|
||
|
|
+ bool changed;
|
||
|
|
+ int i = 0;
|
||
|
|
+ do
|
||
|
|
+ {
|
||
|
|
+ changed = false;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Iteration %d\n", i);
|
||
|
|
+ for (type_alias_map::iterator it = fta_map->begin ();
|
||
|
|
+ it != fta_map->end (); ++it)
|
||
|
|
+ {
|
||
|
|
+ tree type = (*type_uid_map)[it->first];
|
||
|
|
+ if (TYPE_CANONICAL (type))
|
||
|
|
+ continue;
|
||
|
|
+ hashval_t hash = get_hash_for_ftype (type, incomplete_hash_ftype);
|
||
|
|
+ if (incomplete_hash_ftype->count (TYPE_UID (type)) != 0)
|
||
|
|
+ {
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "Incomplete (%d), h=%u\n", TYPE_UID (type),
|
||
|
|
+ (unsigned int) hash);
|
||
|
|
+ continue;
|
||
|
|
+ }
|
||
|
|
+ if (hash_to_ftype.count (hash) == 0)
|
||
|
|
+ hash_to_ftype[hash] = type;
|
||
|
|
+ TYPE_CANONICAL (type) = hash_to_ftype[hash];
|
||
|
|
+ changed = true;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ fprintf (dump_file, "(%d)->(%d), h=%u\n", TYPE_UID (type),
|
||
|
|
+ TYPE_UID (TYPE_CANONICAL (type)), (unsigned int) hash);
|
||
|
|
+ }
|
||
|
|
+ i++;
|
||
|
|
+ }
|
||
|
|
+ while (changed);
|
||
|
|
+
|
||
|
|
+ delete incomplete_hash_ftype;
|
||
|
|
+ delete icp_canonical_types;
|
||
|
|
+ delete canonical_type_hash_cache;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_function_type_aliases_list ()
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, "\nList of function type aliases:\n");
|
||
|
|
+ for (type_alias_map::iterator it = fta_map->begin ();
|
||
|
|
+ it != fta_map->end (); ++it)
|
||
|
|
+ dump_type_uid_with_set ("(%d) ", (*type_uid_map)[it->first], fta_map);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Collect type aliases and find missed canonical types. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+collect_function_type_aliases ()
|
||
|
|
+{
|
||
|
|
+ collect_type_alias_sets ();
|
||
|
|
+ process_cbase_to_ptype_map ();
|
||
|
|
+ process_alias_type_sets ();
|
||
|
|
+
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_unsafe_and_canonical_types ();
|
||
|
|
+
|
||
|
|
+ /* TODO: maybe remove this pass. */
|
||
|
|
+ init_function_type_aliases ();
|
||
|
|
+ for (type_alias_map::iterator it = fta_map->begin ();
|
||
|
|
+ it != fta_map->end (); ++it)
|
||
|
|
+ set_canonical_type_for_type_set (it->second);
|
||
|
|
+ find_type_aliases_by_compatibility ();
|
||
|
|
+
|
||
|
|
+ if (dump_file && (dump_flags & TDF_DETAILS))
|
||
|
|
+ dump_function_type_aliases_list ();
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_function_signature_info (struct cgraph_node *n, tree ftype, bool varargs)
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, "Function decl: ");
|
||
|
|
+ print_generic_expr (dump_file, n->decl);
|
||
|
|
+ dump_type_uid_with_set (" with type (%d) ", ftype, fta_map, true, false);
|
||
|
|
+ if (varargs)
|
||
|
|
+ fprintf (dump_file, "has varargs, ");
|
||
|
|
+ if (TREE_CODE (ftype) == METHOD_TYPE)
|
||
|
|
+ fprintf (dump_file, "is method, ");
|
||
|
|
+ if (!n->address_taken)
|
||
|
|
+ fprintf (dump_file, "is not address taken, ");
|
||
|
|
+ if (unsafe_types->count (TYPE_UID (ftype)))
|
||
|
|
+ fprintf (dump_file, "is unsafe, ");
|
||
|
|
+ fprintf (dump_file, "\n");
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Check if the function has variadic arguments.
|
||
|
|
+ It's corrected count_num_arguments (). */
|
||
|
|
+
|
||
|
|
+static bool
|
||
|
|
+has_varargs (tree decl)
|
||
|
|
+{
|
||
|
|
+ tree t;
|
||
|
|
+ unsigned int num = 0;
|
||
|
|
+ for (t = TYPE_ARG_TYPES (TREE_TYPE (decl));
|
||
|
|
+ t && TREE_VALUE (t) != void_type_node; t = TREE_CHAIN (t))
|
||
|
|
+ num++;
|
||
|
|
+ if (!t && num)
|
||
|
|
+ return true;
|
||
|
|
+ return false;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Join fs_map's sets for function type aliases. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+merge_fs_map_for_ftype_aliases ()
|
||
|
|
+{
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "\n\nMerge decl sets for function type aliases:\n");
|
||
|
|
+ type_set processed_types;
|
||
|
|
+ for (type_decl_map::iterator it1 = fs_map->begin ();
|
||
|
|
+ it1 != fs_map->end (); ++it1)
|
||
|
|
+ {
|
||
|
|
+ if (processed_types.count (it1->first) != 0)
|
||
|
|
+ continue;
|
||
|
|
+ decl_set *d_set = it1->second;
|
||
|
|
+ tree type = (*type_uid_map)[it1->first];
|
||
|
|
+ type_set *set = (*fta_map)[it1->first];
|
||
|
|
+ for (type_set::const_iterator it2 = set->begin ();
|
||
|
|
+ it2 != set->end (); it2++)
|
||
|
|
+ {
|
||
|
|
+ tree t2 = (*type_uid_map)[*it2];
|
||
|
|
+ processed_types.insert (*it2);
|
||
|
|
+ if (type == t2)
|
||
|
|
+ continue;
|
||
|
|
+ gcc_assert ((TREE_CODE (type) == FUNCTION_TYPE
|
||
|
|
+ || TREE_CODE (type) == METHOD_TYPE)
|
||
|
|
+ && (TREE_CODE (t2) == FUNCTION_TYPE
|
||
|
|
+ || TREE_CODE (t2) == METHOD_TYPE));
|
||
|
|
+ if (fs_map->count (*it2) == 0 || (*fs_map)[*it2] == NULL)
|
||
|
|
+ (*fs_map)[*it2] = d_set;
|
||
|
|
+ else
|
||
|
|
+ {
|
||
|
|
+ decl_set *t2_decl_set = (*fs_map)[*it2];
|
||
|
|
+ (*fs_map)[*it2] = d_set;
|
||
|
|
+ gcc_assert (t2_decl_set && t2_decl_set->size() > 0);
|
||
|
|
+ d_set->insert (t2_decl_set->begin (), t2_decl_set->end ());
|
||
|
|
+ delete t2_decl_set;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Dump function types with set of functions corresponding to it. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_function_signature_sets ()
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, "\n\nUnique sets of function signatures:\n");
|
||
|
|
+ std::set<decl_set *> processed_sets;
|
||
|
|
+ for (type_decl_map::iterator it1 = fs_map->begin ();
|
||
|
|
+ it1 != fs_map->end (); ++it1)
|
||
|
|
+ {
|
||
|
|
+ decl_set *set = it1->second;
|
||
|
|
+ if (processed_sets.count (set) != 0)
|
||
|
|
+ continue;
|
||
|
|
+ processed_sets.insert (set);
|
||
|
|
+ fprintf (dump_file, "{ ");
|
||
|
|
+ print_type_set (it1->first, fta_map);
|
||
|
|
+ fprintf (dump_file, " : ");
|
||
|
|
+ for (decl_set::const_iterator it2 = set->begin ();
|
||
|
|
+ it2 != set->end (); it2++)
|
||
|
|
+ {
|
||
|
|
+ fprintf (dump_file, it2 == set->begin () ? "" : ", ");
|
||
|
|
+ print_generic_expr (dump_file, *it2);
|
||
|
|
+ fprintf (dump_file, "(%d)", DECL_UID (*it2));
|
||
|
|
+ }
|
||
|
|
+ fprintf (dump_file, "}\n");
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Fill the map of function types to sets of function decls. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+collect_function_signatures ()
|
||
|
|
+{
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "\n\nCollect function signatures:\n");
|
||
|
|
+ struct cgraph_node *n;
|
||
|
|
+ FOR_EACH_FUNCTION (n)
|
||
|
|
+ {
|
||
|
|
+ gcc_assert (n->decl && TREE_TYPE (n->decl));
|
||
|
|
+ tree ftype = TREE_TYPE (n->decl);
|
||
|
|
+ bool varargs = has_varargs (n->decl);
|
||
|
|
+ if (varargs && n->address_taken)
|
||
|
|
+ has_address_taken_functions_with_varargs = true;
|
||
|
|
+ if (dump_file)
|
||
|
|
+ dump_function_signature_info (n, ftype, varargs);
|
||
|
|
+ if (!n->address_taken)
|
||
|
|
+ continue;
|
||
|
|
+ /* TODO: make a separate pass at the end to remove canonicals. */
|
||
|
|
+ tree ctype = TYPE_CANONICAL (ftype);
|
||
|
|
+ unsigned alias_type_fs = ctype ? TYPE_UID (ctype) : 0;
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "canonical type: %d %ld\n",
|
||
|
|
+ alias_type_fs, fs_map->count (alias_type_fs));
|
||
|
|
+ if (alias_type_fs)
|
||
|
|
+ {
|
||
|
|
+ if (fs_map->count (TYPE_UID (ctype)) == 0)
|
||
|
|
+ (*fs_map)[TYPE_UID (ctype)] = new decl_set ();
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "insert decl (%d) to set of map [%d]\n",
|
||
|
|
+ DECL_UID (n->decl), TYPE_UID (ctype));
|
||
|
|
+ (*fs_map)[TYPE_UID (ctype)]->insert (n->decl);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ merge_fs_map_for_ftype_aliases ();
|
||
|
|
+ if (dump_file)
|
||
|
|
+ dump_function_signature_sets ();
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+#define MAX_TARG_STAT 4
|
||
|
|
+struct icp_stats
|
||
|
|
+{
|
||
|
|
+ int npolymorphic;
|
||
|
|
+ int nspeculated;
|
||
|
|
+ int nsubst;
|
||
|
|
+ int ncold;
|
||
|
|
+ int nmultiple;
|
||
|
|
+ int noverwritable;
|
||
|
|
+ int nnotdefined;
|
||
|
|
+ int nexternal;
|
||
|
|
+ int nartificial;
|
||
|
|
+ int nremove;
|
||
|
|
+ int nicp;
|
||
|
|
+ int nspec;
|
||
|
|
+ int nf;
|
||
|
|
+ int ncalls;
|
||
|
|
+ int nindir;
|
||
|
|
+ int nind_only;
|
||
|
|
+ int ntargs[MAX_TARG_STAT + 1];
|
||
|
|
+};
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_processing_function (struct cgraph_node *n, struct icp_stats &stats)
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, "\n\nProcesing function %s\n", n->dump_name ());
|
||
|
|
+ print_generic_expr (dump_file, n->decl);
|
||
|
|
+ fprintf (dump_file, "\n");
|
||
|
|
+ dump_type_with_uid ("Func's type: ", TREE_TYPE (n->decl));
|
||
|
|
+ if (dump_file && (dump_flags & TDF_STATS))
|
||
|
|
+ {
|
||
|
|
+ struct cgraph_edge *e;
|
||
|
|
+ stats.nf++;
|
||
|
|
+ for (e = n->indirect_calls; e; e = e->next_callee)
|
||
|
|
+ stats.nindir++;
|
||
|
|
+ for (e = n->callees; e; e = e->next_callee)
|
||
|
|
+ stats.ncalls++;
|
||
|
|
+ stats.ncalls += stats.nindir;
|
||
|
|
+ if (n->callers == NULL)
|
||
|
|
+ {
|
||
|
|
+ fprintf (dump_file, "Function has NO callers\n");
|
||
|
|
+ stats.nind_only++;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_indirect_call_site (tree call_fn, tree call_fn_ty)
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, "Indirect call site: ");
|
||
|
|
+ print_generic_expr (dump_file, call_fn);
|
||
|
|
+ dump_type_with_uid ("\nFunction pointer type: ", call_fn_ty);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+erase_from_unreachable (unsigned type_uid, type_set &unreachable)
|
||
|
|
+{
|
||
|
|
+ unreachable.erase (type_uid);
|
||
|
|
+ if (!fta_map->count (type_uid))
|
||
|
|
+ return;
|
||
|
|
+ type_set *set = (*fta_map)[type_uid];
|
||
|
|
+ for (type_set::const_iterator it = set->begin (); it != set->end (); it++)
|
||
|
|
+ unreachable.erase (*it);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_found_fdecls (decl_set *decls, unsigned ctype_uid)
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, "Signature analysis FOUND decls (%d):", ctype_uid);
|
||
|
|
+ for (decl_set::const_iterator it = decls->begin (); it != decls->end (); it++)
|
||
|
|
+ {
|
||
|
|
+ print_generic_expr (dump_file, *it);
|
||
|
|
+ fprintf (dump_file, "(%d), ", DECL_UID (*it));
|
||
|
|
+ }
|
||
|
|
+ if (unsafe_types->count (ctype_uid))
|
||
|
|
+ fprintf (dump_file, "type is UNSAFE");
|
||
|
|
+ fprintf (dump_file, "\n");
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+count_found_targets (struct icp_stats &stats, unsigned size)
|
||
|
|
+{
|
||
|
|
+ gcc_assert (size > 0);
|
||
|
|
+ stats.ntargs[size > MAX_TARG_STAT ? MAX_TARG_STAT : size - 1]++;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Promote the indirect call. */
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+promote_call (struct cgraph_edge *e, struct cgraph_node *n,
|
||
|
|
+ struct cgraph_node *likely_target, struct icp_stats *stats)
|
||
|
|
+{
|
||
|
|
+ if (dump_enabled_p ())
|
||
|
|
+ {
|
||
|
|
+ dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, e->call_stmt,
|
||
|
|
+ "promoting indirect call in %s to %s\n",
|
||
|
|
+ n->dump_name (), likely_target->dump_name ());
|
||
|
|
+ }
|
||
|
|
+ if (!likely_target->can_be_discarded_p ())
|
||
|
|
+ {
|
||
|
|
+ symtab_node *sn = likely_target->noninterposable_alias ();
|
||
|
|
+ cgraph_node *alias = dyn_cast<cgraph_node *> (sn);
|
||
|
|
+ if (alias)
|
||
|
|
+ likely_target = alias;
|
||
|
|
+ }
|
||
|
|
+ gimple *new_call;
|
||
|
|
+ if (flag_icp_speculatively)
|
||
|
|
+ {
|
||
|
|
+ e->make_speculative (likely_target, e->count.apply_scale (5, 10));
|
||
|
|
+ new_call = e->call_stmt;
|
||
|
|
+ stats->nspec++;
|
||
|
|
+ }
|
||
|
|
+ else
|
||
|
|
+ {
|
||
|
|
+ cgraph_edge *e2 = cgraph_edge::make_direct (e, likely_target);
|
||
|
|
+ new_call = cgraph_edge::redirect_call_stmt_to_callee (e2);
|
||
|
|
+ stats->nsubst++;
|
||
|
|
+ }
|
||
|
|
+ if (dump_file)
|
||
|
|
+ {
|
||
|
|
+ fprintf (dump_file, "The call is substituted by: ");
|
||
|
|
+ print_gimple_stmt (dump_file, new_call, 0);
|
||
|
|
+ fprintf (dump_file, "\n");
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Find functions which are called only indirectly and if they are not in
|
||
|
|
+ fs_map, they can be removed. For now it is used only to print stats. */
|
||
|
|
+
|
||
|
|
+static int
|
||
|
|
+find_functions_can_be_removed (type_set &unreachable)
|
||
|
|
+{
|
||
|
|
+ int nremove = 0;
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "\nRemove unused functions:\n");
|
||
|
|
+ struct cgraph_node *n;
|
||
|
|
+ FOR_EACH_FUNCTION (n)
|
||
|
|
+ {
|
||
|
|
+ gcc_assert (n->decl && TREE_TYPE (n->decl));
|
||
|
|
+ if (n->callers != NULL)
|
||
|
|
+ continue;
|
||
|
|
+ tree ftype = TREE_TYPE (n->decl);
|
||
|
|
+ tree ctype = TYPE_CANONICAL (ftype);
|
||
|
|
+ if (!ctype || !unreachable.count (TYPE_UID (ctype))
|
||
|
|
+ || unsafe_types->count (TYPE_UID (ftype))
|
||
|
|
+ || TREE_CODE (ftype) == METHOD_TYPE || n->callers != NULL
|
||
|
|
+ || !n->definition || n->alias || n->thunk || n->clones)
|
||
|
|
+ continue;
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "%s is not used\n", n->dump_name ());
|
||
|
|
+ nremove++;
|
||
|
|
+ }
|
||
|
|
+ return nremove;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void
|
||
|
|
+dump_stats (struct icp_stats &st)
|
||
|
|
+{
|
||
|
|
+ fprintf (dump_file, "\nSTATS: %i candidates for indirect call promotion,"
|
||
|
|
+ " %i substituted, %i speculatively promoted, %i cold\n"
|
||
|
|
+ "%i have multiple targets, %i already speculated, %i external,"
|
||
|
|
+ " %i not defined, %i artificial, %i polymorphic calls,"
|
||
|
|
+ " %i overwritable\n", st.nicp, st.nsubst, st.nspec, st.ncold,
|
||
|
|
+ st.nmultiple, st.nspeculated, st.nexternal, st.nnotdefined,
|
||
|
|
+ st.nartificial, st.npolymorphic, st.noverwritable);
|
||
|
|
+ if (!(dump_flags & TDF_STATS))
|
||
|
|
+ return;
|
||
|
|
+ fprintf (dump_file, "EXTRA STATS: %i functions, %i indirect calls,"
|
||
|
|
+ " %i total calls, %i called only indirectly, %i may be removed\n"
|
||
|
|
+ "Indirect call sites with found targets ", st.nf, st.nindir,
|
||
|
|
+ st.ncalls, st.nind_only, st.nremove);
|
||
|
|
+ for (unsigned i = 0; i < MAX_TARG_STAT; i++)
|
||
|
|
+ fprintf (dump_file, "%u:%i, ", i + 1, st.ntargs[i]);
|
||
|
|
+ fprintf (dump_file, "more:%i\n", st.ntargs[MAX_TARG_STAT]);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Optimize indirect calls. When an indirect call has only one target,
|
||
|
|
+ promote it into a direct call. */
|
||
|
|
+
|
||
|
|
+static bool
|
||
|
|
+optimize_indirect_calls ()
|
||
|
|
+{
|
||
|
|
+ /* TODO: maybe move to the top of ipa_icp. */
|
||
|
|
+ if (has_address_taken_functions_with_varargs)
|
||
|
|
+ {
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "\n\nAddress taken function with varargs is found."
|
||
|
|
+ " Skip the optimization.\n");
|
||
|
|
+ return false;
|
||
|
|
+ }
|
||
|
|
+ struct icp_stats stats = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
|
|
+ 0, 0, 0, 0, 0, {0, 0, 0, 0, 0}};
|
||
|
|
+ /* At first assume all function types are unreadchable. */
|
||
|
|
+ type_set unreachable_ftypes;
|
||
|
|
+ if (dump_file && (dump_flags & TDF_STATS))
|
||
|
|
+ for (type_decl_map::iterator it = fs_map->begin ();
|
||
|
|
+ it != fs_map->end (); ++it)
|
||
|
|
+ unreachable_ftypes.insert (it->first);
|
||
|
|
+
|
||
|
|
+ struct cgraph_node *n;
|
||
|
|
+ FOR_EACH_DEFINED_FUNCTION (n)
|
||
|
|
+ {
|
||
|
|
+ if (dump_file)
|
||
|
|
+ dump_processing_function (n, stats);
|
||
|
|
+ struct cgraph_edge *e;
|
||
|
|
+ bool update = false;
|
||
|
|
+ if (!opt_for_fn (n->decl, flag_icp) || !n->has_gimple_body_p ()
|
||
|
|
+ || n->inlined_to || !n->indirect_calls)
|
||
|
|
+ {
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "Skip the function\n");
|
||
|
|
+ continue;
|
||
|
|
+ }
|
||
|
|
+ /* If the function has indirect calls which are not polymorphic,
|
||
|
|
+ process its body, otherwise continue. */
|
||
|
|
+ bool non_polymorphic_calls = false;
|
||
|
|
+ for (e = n->indirect_calls; e; e = e->next_callee)
|
||
|
|
+ if (!e->indirect_info->polymorphic)
|
||
|
|
+ {
|
||
|
|
+ non_polymorphic_calls = true;
|
||
|
|
+ break;
|
||
|
|
+ }
|
||
|
|
+ if (!non_polymorphic_calls)
|
||
|
|
+ {
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "All indirect calls are polymorphic,"
|
||
|
|
+ "skip...\n");
|
||
|
|
+ continue;
|
||
|
|
+ }
|
||
|
|
+ /* Get the function body to operate with call statements. */
|
||
|
|
+ n->get_body ();
|
||
|
|
+ /* Walk indirect call sites and apply the optimization. */
|
||
|
|
+ cgraph_edge *next;
|
||
|
|
+ for (e = n->indirect_calls; e; e = next)
|
||
|
|
+ {
|
||
|
|
+ next = e->next_callee;
|
||
|
|
+ if (e->indirect_info->polymorphic)
|
||
|
|
+ {
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "Target is polymorphic, skip...\n\n");
|
||
|
|
+ stats.npolymorphic++;
|
||
|
|
+ continue;
|
||
|
|
+ }
|
||
|
|
+ stats.nicp++;
|
||
|
|
+ struct cgraph_node *likely_target = NULL;
|
||
|
|
+ gcall *stmt = e->call_stmt;
|
||
|
|
+ gcc_assert (stmt != NULL);
|
||
|
|
+ tree call_fn = gimple_call_fn (stmt);
|
||
|
|
+ tree call_fn_ty = TREE_TYPE (call_fn);
|
||
|
|
+ if (dump_file)
|
||
|
|
+ dump_indirect_call_site (call_fn, call_fn_ty);
|
||
|
|
+ tree decl = NULL_TREE;
|
||
|
|
+ if (POINTER_TYPE_P (call_fn_ty))
|
||
|
|
+ {
|
||
|
|
+ if (dump_file)
|
||
|
|
+ dump_type_with_uid ("Pointee type: ", TREE_TYPE (call_fn_ty));
|
||
|
|
+ if (dump_file && (dump_flags & TDF_STATS))
|
||
|
|
+ erase_from_unreachable (TYPE_UID (TREE_TYPE (call_fn_ty)),
|
||
|
|
+ unreachable_ftypes);
|
||
|
|
+ /* Try to use the signature analysis results. */
|
||
|
|
+ tree ctype = TYPE_CANONICAL (TREE_TYPE (call_fn_ty));
|
||
|
|
+ unsigned ctype_uid = ctype ? TYPE_UID (ctype) : 0;
|
||
|
|
+ if (ctype_uid && fs_map->count (ctype_uid))
|
||
|
|
+ {
|
||
|
|
+ if (dump_flags && (dump_flags & TDF_STATS))
|
||
|
|
+ erase_from_unreachable (ctype_uid, unreachable_ftypes);
|
||
|
|
+ decl_set *decls = (*fs_map)[ctype_uid];
|
||
|
|
+ if (dump_file)
|
||
|
|
+ dump_found_fdecls (decls, ctype_uid);
|
||
|
|
+ /* TODO: optimize for multple targets. */
|
||
|
|
+ if (!unsafe_types->count (ctype_uid) && decls->size () == 1)
|
||
|
|
+ {
|
||
|
|
+ decl = *(decls->begin ());
|
||
|
|
+ likely_target = cgraph_node::get (decl);
|
||
|
|
+ }
|
||
|
|
+ if (!unsafe_types->count (ctype_uid)
|
||
|
|
+ && (dump_flags & TDF_STATS))
|
||
|
|
+ count_found_targets (stats, decls->size ());
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ if (!decl || !likely_target)
|
||
|
|
+ {
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "Callee is unknown\n\n");
|
||
|
|
+ continue;
|
||
|
|
+ }
|
||
|
|
+ if (TREE_CODE (TREE_TYPE (decl)) == METHOD_TYPE)
|
||
|
|
+ {
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "Callee is method\n\n");
|
||
|
|
+ continue;
|
||
|
|
+ }
|
||
|
|
+ if (e->speculative)
|
||
|
|
+ {
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "Call is already speculated\n\n");
|
||
|
|
+ stats.nspeculated++;
|
||
|
|
+ continue;
|
||
|
|
+ }
|
||
|
|
+ if (!likely_target->definition)
|
||
|
|
+ {
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "Target is not a definition\n\n");
|
||
|
|
+ stats.nnotdefined++;
|
||
|
|
+ continue;
|
||
|
|
+ }
|
||
|
|
+ /* Do not introduce new references to external symbols. While we
|
||
|
|
+ can handle these just well, it is common for programs to
|
||
|
|
+ incorrectly with headers defining methods they are linked
|
||
|
|
+ with. */
|
||
|
|
+ if (DECL_EXTERNAL (likely_target->decl))
|
||
|
|
+ {
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "Target is external\n\n");
|
||
|
|
+ stats.nexternal++;
|
||
|
|
+ continue;
|
||
|
|
+ }
|
||
|
|
+ /* Don't use an implicitly-declared destructor (c++/58678). */
|
||
|
|
+ struct cgraph_node *non_thunk_target
|
||
|
|
+ = likely_target->function_symbol ();
|
||
|
|
+ if (DECL_ARTIFICIAL (non_thunk_target->decl))
|
||
|
|
+ {
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "Target is artificial\n\n");
|
||
|
|
+ stats.nartificial++;
|
||
|
|
+ continue;
|
||
|
|
+ }
|
||
|
|
+ if (likely_target->get_availability () <= AVAIL_INTERPOSABLE
|
||
|
|
+ && likely_target->can_be_discarded_p ())
|
||
|
|
+ {
|
||
|
|
+ if (dump_file)
|
||
|
|
+ fprintf (dump_file, "Target is overwritable\n\n");
|
||
|
|
+ stats.noverwritable++;
|
||
|
|
+ continue;
|
||
|
|
+ }
|
||
|
|
+ else if (dbg_cnt (icp))
|
||
|
|
+ {
|
||
|
|
+ promote_call (e, n, likely_target, &stats);
|
||
|
|
+ update = true;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ if (update)
|
||
|
|
+ ipa_update_overall_fn_summary (n);
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if (dump_file && (dump_flags & TDF_STATS))
|
||
|
|
+ stats.nremove = find_functions_can_be_removed (unreachable_ftypes);
|
||
|
|
+
|
||
|
|
+ if (dump_file)
|
||
|
|
+ dump_stats (stats);
|
||
|
|
+ return stats.nsubst || stats.nspec;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* Delete the given MAP with allocated sets. One set may be associated with
|
||
|
|
+ more then one type/decl. */
|
||
|
|
+
|
||
|
|
+template <typename MAP>
|
||
|
|
+static void
|
||
|
|
+remove_type_alias_map (MAP *map)
|
||
|
|
+{
|
||
|
|
+ std::set<typename MAP::mapped_type> processed_sets;
|
||
|
|
+ for (typename MAP::iterator it = map->begin (); it != map->end (); it++)
|
||
|
|
+ {
|
||
|
|
+ typename MAP::mapped_type set = it->second;
|
||
|
|
+ if (processed_sets.count (set) != 0)
|
||
|
|
+ continue;
|
||
|
|
+ processed_sets.insert (set);
|
||
|
|
+ delete set;
|
||
|
|
+ }
|
||
|
|
+ delete map;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* The ipa indirect call promotion pass. Run required analysis and optimize
|
||
|
|
+ indirect calls.
|
||
|
|
+ When indirect call has only one target, promote it into a direct call. */
|
||
|
|
+
|
||
|
|
+static unsigned int
|
||
|
|
+ipa_icp (void)
|
||
|
|
+{
|
||
|
|
+ ta_map = new type_alias_map;
|
||
|
|
+ fta_map = new type_alias_map;
|
||
|
|
+ cbase_to_ptype = new type_alias_map;
|
||
|
|
+ fs_map = new type_decl_map;
|
||
|
|
+ ctype_map = new type_map;
|
||
|
|
+ unsafe_types = new type_set;
|
||
|
|
+ type_uid_map = new uid_to_type_map;
|
||
|
|
+
|
||
|
|
+ /* Find type aliases, fill the function signature map and
|
||
|
|
+ optimize indirect calls. */
|
||
|
|
+ collect_function_type_aliases ();
|
||
|
|
+ collect_function_signatures ();
|
||
|
|
+ bool optimized = optimize_indirect_calls ();
|
||
|
|
+
|
||
|
|
+ remove_type_alias_map (ta_map);
|
||
|
|
+ remove_type_alias_map (fta_map);
|
||
|
|
+ remove_type_alias_map (cbase_to_ptype);
|
||
|
|
+ remove_type_alias_map (fs_map);
|
||
|
|
+ delete ctype_map;
|
||
|
|
+ delete unsafe_types;
|
||
|
|
+ delete type_uid_map;
|
||
|
|
+
|
||
|
|
+ return optimized ? TODO_remove_functions : 0;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+namespace {
|
||
|
|
+
|
||
|
|
+const pass_data pass_data_ipa_icp =
|
||
|
|
+{
|
||
|
|
+ IPA_PASS, /* type */
|
||
|
|
+ "icp", /* name */
|
||
|
|
+ OPTGROUP_NONE, /* optinfo_flags */
|
||
|
|
+ TV_IPA_ICP, /* tv_id */
|
||
|
|
+ 0, /* properties_required */
|
||
|
|
+ 0, /* properties_provided */
|
||
|
|
+ 0, /* properties_destroyed */
|
||
|
|
+ 0, /* todo_flags_start */
|
||
|
|
+ 0, /* todo_flags_finish */
|
||
|
|
+};
|
||
|
|
+
|
||
|
|
+class pass_ipa_icp : public ipa_opt_pass_d
|
||
|
|
+{
|
||
|
|
+public:
|
||
|
|
+ pass_ipa_icp (gcc::context *ctxt)
|
||
|
|
+ : ipa_opt_pass_d (pass_data_ipa_icp, ctxt,
|
||
|
|
+ NULL, /* generate_summary */
|
||
|
|
+ NULL, /* write_summary */
|
||
|
|
+ NULL, /* read_summary */
|
||
|
|
+ NULL, /* write_optimization_summary */
|
||
|
|
+ NULL, /* read_optimization_summary */
|
||
|
|
+ NULL, /* stmt_fixup */
|
||
|
|
+ 0, /* function_transform_todo_flags_start */
|
||
|
|
+ NULL, /* function_transform */
|
||
|
|
+ NULL) /* variable_transform */
|
||
|
|
+ {}
|
||
|
|
+
|
||
|
|
+ /* opt_pass methods: */
|
||
|
|
+ virtual bool gate (function *)
|
||
|
|
+ {
|
||
|
|
+ return (optimize && flag_icp && !seen_error ()
|
||
|
|
+ && (in_lto_p || flag_whole_program));
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ virtual unsigned int execute (function *) { return ipa_icp (); }
|
||
|
|
+
|
||
|
|
+}; // class pass_ipa_icp
|
||
|
|
+
|
||
|
|
+} // anon namespace
|
||
|
|
+
|
||
|
|
+ipa_opt_pass_d *
|
||
|
|
+make_pass_ipa_icp (gcc::context *ctxt)
|
||
|
|
+{
|
||
|
|
+ return new pass_ipa_icp (ctxt);
|
||
|
|
+}
|
||
|
|
|
||
|
|
#include "gt-ipa-devirt.h"
|
||
|
|
diff --git a/gcc/passes.def b/gcc/passes.def
|
||
|
|
index 9692066e4..d6db9be6e 100644
|
||
|
|
--- a/gcc/passes.def
|
||
|
|
+++ b/gcc/passes.def
|
||
|
|
@@ -156,6 +156,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_icp);
|
||
|
|
NEXT_PASS (pass_ipa_cp);
|
||
|
|
NEXT_PASS (pass_ipa_sra);
|
||
|
|
NEXT_PASS (pass_ipa_cdtor_merge);
|
||
|
|
diff --git a/gcc/testsuite/gcc.dg/icp1.c b/gcc/testsuite/gcc.dg/icp1.c
|
||
|
|
new file mode 100644
|
||
|
|
index 000000000..c2117f738
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/gcc/testsuite/gcc.dg/icp1.c
|
||
|
|
@@ -0,0 +1,40 @@
|
||
|
|
+/* { dg-do run } */
|
||
|
|
+/* { dg-options "-O2 -flto -ficp -fdump-ipa-icp=./icp1.c.077i.icp" } */
|
||
|
|
+
|
||
|
|
+int dummy = 0;
|
||
|
|
+
|
||
|
|
+typedef int (*ftype1)(int a);
|
||
|
|
+typedef float (*ftype2)(int a);
|
||
|
|
+
|
||
|
|
+ftype1 func1;
|
||
|
|
+
|
||
|
|
+struct {
|
||
|
|
+ int a;
|
||
|
|
+ int* b;
|
||
|
|
+ ftype1 myf1;
|
||
|
|
+ ftype2 myf2;
|
||
|
|
+} my_str;
|
||
|
|
+
|
||
|
|
+int foo(int a) {
|
||
|
|
+ my_str.myf1 = func1;
|
||
|
|
+ if (a % 2 == 0)
|
||
|
|
+ dummy += dummy % (dummy - a);
|
||
|
|
+ return a + 1;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+float bar(int a) {
|
||
|
|
+ my_str.myf2 = &bar;
|
||
|
|
+ func1 = &foo;
|
||
|
|
+ return foo(a);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int main() {
|
||
|
|
+ bar(1);
|
||
|
|
+ my_str.myf2(3);
|
||
|
|
+ return (my_str.myf1(2) + func1(4)) != 8;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* { dg-final { scan-ipa-dump "The call is substituted by:.*= foo \\(4\\);" "icp" } } */
|
||
|
|
+/* { dg-final { scan-ipa-dump "The call is substituted by:.*= foo \\(2\\);" "icp" } } */
|
||
|
|
+/* { dg-final { scan-ipa-dump "The call is substituted by: bar \\(3\\);" "icp" } } */
|
||
|
|
+/* { dg-final { scan-ipa-dump "STATS: 3 candidates for indirect call promotion, 3 substituted, 0 speculatively promoted, 0 cold" "icp" } } */
|
||
|
|
diff --git a/gcc/testsuite/gcc.dg/icp2.c b/gcc/testsuite/gcc.dg/icp2.c
|
||
|
|
new file mode 100644
|
||
|
|
index 000000000..03d31d407
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/gcc/testsuite/gcc.dg/icp2.c
|
||
|
|
@@ -0,0 +1,38 @@
|
||
|
|
+/* { dg-do run } */
|
||
|
|
+/* { dg-options "-O2 -flto -ficp -fdump-ipa-icp=./icp2.c.077i.icp" } */
|
||
|
|
+
|
||
|
|
+int dummy = 0;
|
||
|
|
+
|
||
|
|
+typedef int (*ftype1)(int a);
|
||
|
|
+typedef float (*ftype2)(int a);
|
||
|
|
+
|
||
|
|
+ftype1 func1;
|
||
|
|
+
|
||
|
|
+struct {
|
||
|
|
+ int a;
|
||
|
|
+ int* b;
|
||
|
|
+ ftype1 myf1;
|
||
|
|
+ ftype2 myf2;
|
||
|
|
+} my_str;
|
||
|
|
+
|
||
|
|
+int foo(int a) {
|
||
|
|
+ my_str.myf1 = func1;
|
||
|
|
+ if (a % 2 == 0)
|
||
|
|
+ dummy += dummy % (dummy - a);
|
||
|
|
+ return a + 1;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+float bar(int a) {
|
||
|
|
+ my_str.myf2 = dummy ? (ftype2) &foo : &bar;
|
||
|
|
+ func1 = (ftype1) &bar;
|
||
|
|
+ return foo(a);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int main() {
|
||
|
|
+ bar(1);
|
||
|
|
+ my_str.myf2(3);
|
||
|
|
+ return (my_str.myf1(2) + func1(4)) != 8;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* { dg-final { scan-ipa-dump-not "The call is substituted by.*" "icp" } } */
|
||
|
|
+/* { dg-final { scan-ipa-dump "STATS: 3 candidates for indirect call promotion, 0 substituted, 0 speculatively promoted, 0 cold" "icp" } } */
|
||
|
|
diff --git a/gcc/testsuite/gcc.dg/icp3.c b/gcc/testsuite/gcc.dg/icp3.c
|
||
|
|
new file mode 100644
|
||
|
|
index 000000000..2a7d1e6f5
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/gcc/testsuite/gcc.dg/icp3.c
|
||
|
|
@@ -0,0 +1,52 @@
|
||
|
|
+/* { dg-do run } */
|
||
|
|
+/* { dg-options "-O2 -flto -ficp -fdump-ipa-icp=./icp3.c.077i.icp" } */
|
||
|
|
+
|
||
|
|
+#include <stdio.h>
|
||
|
|
+
|
||
|
|
+int dummy = 0;
|
||
|
|
+
|
||
|
|
+typedef int (*ftype1)(int a);
|
||
|
|
+typedef float (*ftype2)(int a);
|
||
|
|
+typedef ftype1 (*ftype3) (ftype2);
|
||
|
|
+
|
||
|
|
+ftype1 func1;
|
||
|
|
+
|
||
|
|
+struct {
|
||
|
|
+ int a;
|
||
|
|
+ int* b;
|
||
|
|
+ ftype1 myf1;
|
||
|
|
+ ftype2 myf2;
|
||
|
|
+ ftype3 myf3;
|
||
|
|
+} my_str;
|
||
|
|
+
|
||
|
|
+ftype1 boo(ftype2 a) {
|
||
|
|
+ printf ("Call boo\n");
|
||
|
|
+ return (ftype1) a;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int foo(int a) {
|
||
|
|
+ printf ("Call foo\n");
|
||
|
|
+ my_str.myf1 = func1;
|
||
|
|
+ if (a % 2 == 0)
|
||
|
|
+ dummy += dummy % (dummy - a);
|
||
|
|
+ return a + 1;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+float bar(int a) {
|
||
|
|
+ printf("Call bar\n");
|
||
|
|
+ my_str.myf2 = (ftype2) my_str.myf3((ftype2) foo);
|
||
|
|
+ func1 = &foo;
|
||
|
|
+ return foo(a);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int main() {
|
||
|
|
+ my_str.myf3 = &boo;
|
||
|
|
+ bar(1);
|
||
|
|
+ my_str.myf2(3);
|
||
|
|
+ return (my_str.myf1(2) + func1(4)) != 8;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* { dg-final { scan-ipa-dump "The call is substituted by:.*= foo \\(4\\);" "icp" } } */
|
||
|
|
+/* { dg-final { scan-ipa-dump "The call is substituted by:.*= foo \\(2\\);" "icp" } } */
|
||
|
|
+/* { dg-final { scan-ipa-dump "The call is substituted by: foo \\(3\\);" "icp" } } */
|
||
|
|
+/* { dg-final { scan-ipa-dump "STATS: 4 candidates for indirect call promotion, 3 substituted, 0 speculatively promoted, 0 cold" "icp" } } */
|
||
|
|
diff --git a/gcc/testsuite/gcc.dg/icp4.c b/gcc/testsuite/gcc.dg/icp4.c
|
||
|
|
new file mode 100644
|
||
|
|
index 000000000..e3e1d5116
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/gcc/testsuite/gcc.dg/icp4.c
|
||
|
|
@@ -0,0 +1,55 @@
|
||
|
|
+/* { dg-do run } */
|
||
|
|
+/* { dg-options "-O2 -flto -ficp -fdump-ipa-icp=./icp4.c.077i.icp" } */
|
||
|
|
+
|
||
|
|
+#include <stdio.h>
|
||
|
|
+
|
||
|
|
+int dummy = 0;
|
||
|
|
+
|
||
|
|
+typedef int (*ftype1)(int a);
|
||
|
|
+typedef float (*ftype2)(int a);
|
||
|
|
+typedef ftype1 (*ftype3) (ftype2);
|
||
|
|
+
|
||
|
|
+ftype1 func1;
|
||
|
|
+ftype1 boo(ftype2 a);
|
||
|
|
+int foo(int a);
|
||
|
|
+float bar(int a);
|
||
|
|
+
|
||
|
|
+typedef struct {
|
||
|
|
+ int a;
|
||
|
|
+ int* b;
|
||
|
|
+ ftype1 myf1;
|
||
|
|
+ ftype2 myf2;
|
||
|
|
+ ftype3 myf3;
|
||
|
|
+} T;
|
||
|
|
+
|
||
|
|
+T my_str = {0, (int*) &dummy, (ftype1) &boo, (ftype2) &foo, (ftype3) &bar};
|
||
|
|
+
|
||
|
|
+ftype1 boo(ftype2 a) {
|
||
|
|
+ printf ("Call boo\n");
|
||
|
|
+ return (ftype1) a;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int foo(int a) {
|
||
|
|
+ printf ("Call foo\n");
|
||
|
|
+ my_str.myf1 = func1;
|
||
|
|
+ if (a % 2 == 0)
|
||
|
|
+ dummy += dummy % (dummy - a);
|
||
|
|
+ return a + 1;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+float bar(int a) {
|
||
|
|
+ printf("Call bar\n");
|
||
|
|
+ my_str.myf2 = (ftype2) my_str.myf3((ftype2) foo);
|
||
|
|
+ func1 = &foo;
|
||
|
|
+ return foo(a);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int main() {
|
||
|
|
+ my_str.myf3 = &boo;
|
||
|
|
+ bar(1);
|
||
|
|
+ my_str.myf2(3);
|
||
|
|
+ return (my_str.myf1(2) + func1(4)) != 8;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* { dg-final { scan-ipa-dump-not "The call is substituted by.*" "icp" } } */
|
||
|
|
+/* { dg-final { scan-ipa-dump "STATS: 4 candidates for indirect call promotion, 0 substituted, 0 speculatively promoted, 0 cold" "icp" } } */
|
||
|
|
diff --git a/gcc/testsuite/gcc.dg/icp5.c b/gcc/testsuite/gcc.dg/icp5.c
|
||
|
|
new file mode 100644
|
||
|
|
index 000000000..c7709243c
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/gcc/testsuite/gcc.dg/icp5.c
|
||
|
|
@@ -0,0 +1,66 @@
|
||
|
|
+/* { dg-do run } */
|
||
|
|
+/* { dg-options "-O2 -flto -ficp -fdump-ipa-icp=./icp5.c.077i.icp" } */
|
||
|
|
+
|
||
|
|
+#include <stdio.h>
|
||
|
|
+
|
||
|
|
+int dummy = 0;
|
||
|
|
+
|
||
|
|
+typedef int (*ftype1)(int a);
|
||
|
|
+typedef float (*ftype2)(int a);
|
||
|
|
+typedef ftype1 (*ftype3) (ftype2);
|
||
|
|
+
|
||
|
|
+ftype1 func1;
|
||
|
|
+ftype1 boo(ftype2 a);
|
||
|
|
+int foo(int a);
|
||
|
|
+float bar(int a);
|
||
|
|
+
|
||
|
|
+typedef struct {
|
||
|
|
+ int a;
|
||
|
|
+ int* b;
|
||
|
|
+ ftype1 myf1;
|
||
|
|
+ ftype2 myf2;
|
||
|
|
+ ftype3 myf3;
|
||
|
|
+} T;
|
||
|
|
+
|
||
|
|
+T my_str;
|
||
|
|
+
|
||
|
|
+typedef struct {
|
||
|
|
+ int a;
|
||
|
|
+ int* b;
|
||
|
|
+ ftype3 myf1;
|
||
|
|
+ ftype2 myf2;
|
||
|
|
+ ftype1 myf3;
|
||
|
|
+} T1;
|
||
|
|
+
|
||
|
|
+T1 my1 = {0, &dummy, boo, &bar, &foo};
|
||
|
|
+
|
||
|
|
+ftype1 boo(ftype2 a) {
|
||
|
|
+ printf("Call boo\n");
|
||
|
|
+ return (ftype1) a;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int foo(int a) {
|
||
|
|
+ printf("Call foo\n");
|
||
|
|
+ my_str.myf1 = func1;
|
||
|
|
+ if (a % 2 == 0)
|
||
|
|
+ dummy += dummy % (dummy - a);
|
||
|
|
+ return a + 1;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+float bar(int a) {
|
||
|
|
+ printf("Call bar\n");
|
||
|
|
+ my_str.myf2 = (ftype2) my_str.myf3((ftype2) foo);
|
||
|
|
+ func1 = &foo;
|
||
|
|
+ return foo(a);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int main() {
|
||
|
|
+ my_str = *(T*)&my1;
|
||
|
|
+ my_str.myf3 = &boo;
|
||
|
|
+ bar(1);
|
||
|
|
+ my_str.myf2(3);
|
||
|
|
+ return (my_str.myf1(2) + func1(4)) != 8;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* { dg-final { scan-ipa-dump-not "The call is substituted by.*" "icp" } } */
|
||
|
|
+/* { dg-final { scan-ipa-dump "STATS: 4 candidates for indirect call promotion, 0 substituted, 0 speculatively promoted, 0 cold" "icp" } } */
|
||
|
|
diff --git a/gcc/testsuite/gcc.dg/icp6.c b/gcc/testsuite/gcc.dg/icp6.c
|
||
|
|
new file mode 100644
|
||
|
|
index 000000000..5a9f15045
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/gcc/testsuite/gcc.dg/icp6.c
|
||
|
|
@@ -0,0 +1,66 @@
|
||
|
|
+/* { dg-do run } */
|
||
|
|
+/* { dg-options "-O2 -flto -ficp -fdump-ipa-icp=./icp6.c.077i.icp -Wno-int-conversion -Wno-incompatible-pointer-types" } */
|
||
|
|
+int dummy = 0;
|
||
|
|
+
|
||
|
|
+typedef int (*ftype1)(int a);
|
||
|
|
+typedef float (*ftype2)(int a);
|
||
|
|
+typedef int (*ftype3)();
|
||
|
|
+typedef int (*ftype4)(int a, int b);
|
||
|
|
+
|
||
|
|
+ftype1 func1;
|
||
|
|
+ftype4 func2;
|
||
|
|
+
|
||
|
|
+struct {
|
||
|
|
+ int a;
|
||
|
|
+ int* b;
|
||
|
|
+ ftype1 myf1;
|
||
|
|
+ ftype2 myf2;
|
||
|
|
+ ftype3 myf3;
|
||
|
|
+} my_str;
|
||
|
|
+
|
||
|
|
+int foo3(float a) {
|
||
|
|
+ return dummy;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int foo4(int a, int b) {
|
||
|
|
+ return a*b;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int foo(int a) {
|
||
|
|
+ my_str.myf1 = func1;
|
||
|
|
+ if (a % 2 == 0)
|
||
|
|
+ dummy += dummy % (dummy - a);
|
||
|
|
+ return a + 1;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int foo2(float a) {
|
||
|
|
+ func1 = (ftype1) &foo;
|
||
|
|
+ func2 = &foo4;
|
||
|
|
+ return dummy + foo3 (a);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+float bar2(int a) {
|
||
|
|
+ my_str.myf2 = (ftype2)(0x864213);
|
||
|
|
+ func2 = 0x65378;
|
||
|
|
+ return foo(a);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+float bar(int a) {
|
||
|
|
+ my_str.myf3 = &foo2;
|
||
|
|
+ my_str.myf2 = &bar;
|
||
|
|
+ func1 = (ftype1) &dummy;
|
||
|
|
+ func2 = (ftype4) &bar2;
|
||
|
|
+ return foo(a);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int main() {
|
||
|
|
+ bar(1);
|
||
|
|
+ bar2(1);
|
||
|
|
+ bar(0);
|
||
|
|
+ my_str.myf2(3);
|
||
|
|
+ ((ftype1) my_str.myf3)(0.0);
|
||
|
|
+ int sum = func1(4);
|
||
|
|
+ return (sum + my_str.myf1(2) + func2(5, 6)) != 38;
|
||
|
|
+}
|
||
|
|
+/* { dg-final { scan-ipa-dump "The call is substituted by.*foo2 \\(0\\);" "icp" } } */
|
||
|
|
+/* { dg-final { scan-ipa-dump "STATS: 5 candidates for indirect call promotion, 1 substituted, 0 speculatively promoted, 0 cold" "icp" } } */
|
||
|
|
diff --git a/gcc/testsuite/gcc.dg/icp7.c b/gcc/testsuite/gcc.dg/icp7.c
|
||
|
|
new file mode 100644
|
||
|
|
index 000000000..fa52197f4
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/gcc/testsuite/gcc.dg/icp7.c
|
||
|
|
@@ -0,0 +1,48 @@
|
||
|
|
+/* { dg-do run } */
|
||
|
|
+/* { dg-options "-O2 -flto -ficp -fdump-ipa-icp=./icp7.c.077i.icp" } */
|
||
|
|
+
|
||
|
|
+#include <stdarg.h>
|
||
|
|
+
|
||
|
|
+int dummy = 0;
|
||
|
|
+
|
||
|
|
+typedef int (*ftype1)(int a);
|
||
|
|
+typedef float (*ftype2)(int a);
|
||
|
|
+
|
||
|
|
+ftype1 func1;
|
||
|
|
+
|
||
|
|
+struct {
|
||
|
|
+ int a;
|
||
|
|
+ int* b;
|
||
|
|
+ ftype1 myf1;
|
||
|
|
+ ftype2 myf2;
|
||
|
|
+} my_str;
|
||
|
|
+
|
||
|
|
+int boo(int a, ...) {
|
||
|
|
+ va_list ap;
|
||
|
|
+ va_start(ap, a);
|
||
|
|
+ if (a == 0)
|
||
|
|
+ dummy += va_arg(ap, int);
|
||
|
|
+ va_end(ap);
|
||
|
|
+ return dummy;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int foo(int a) {
|
||
|
|
+ my_str.myf1 = func1;
|
||
|
|
+ if (a % 2 == 0)
|
||
|
|
+ dummy += dummy % (dummy - a);
|
||
|
|
+ return a + 1;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+float bar(int a) {
|
||
|
|
+ my_str.myf2 = &bar;
|
||
|
|
+ func1 = (ftype1) &boo;
|
||
|
|
+ return foo(a);
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int main() {
|
||
|
|
+ bar(1);
|
||
|
|
+ my_str.myf2(3);
|
||
|
|
+ return (my_str.myf1(2) + func1(4));
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/* { dg-final { scan-ipa-dump "Address taken function with varargs is found. Skip the optimization." "icp" } } */
|
||
|
|
diff --git a/gcc/timevar.def b/gcc/timevar.def
|
||
|
|
index 98a5a490f..ca4156066 100644
|
||
|
|
--- a/gcc/timevar.def
|
||
|
|
+++ b/gcc/timevar.def
|
||
|
|
@@ -71,6 +71,7 @@ DEFTIMEVAR (TV_CGRAPHOPT , "callgraph optimization")
|
||
|
|
DEFTIMEVAR (TV_CGRAPH_FUNC_EXPANSION , "callgraph functions expansion")
|
||
|
|
DEFTIMEVAR (TV_CGRAPH_IPA_PASSES , "callgraph ipa passes")
|
||
|
|
DEFTIMEVAR (TV_IPA_ODR , "ipa ODR types")
|
||
|
|
+DEFTIMEVAR (TV_IPA_ICP , "ipa indirect call promotion")
|
||
|
|
DEFTIMEVAR (TV_IPA_FNSUMMARY , "ipa function summary")
|
||
|
|
DEFTIMEVAR (TV_IPA_UNREACHABLE , "ipa dead code removal")
|
||
|
|
DEFTIMEVAR (TV_IPA_INHERITANCE , "ipa inheritance graph")
|
||
|
|
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
|
||
|
|
index 56898e019..5f09e4f8b 100644
|
||
|
|
--- a/gcc/tree-pass.h
|
||
|
|
+++ b/gcc/tree-pass.h
|
||
|
|
@@ -524,6 +524,7 @@ 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);
|
||
|
|
extern ipa_opt_pass_d *make_pass_ipa_devirt (gcc::context *ctxt);
|
||
|
|
+extern ipa_opt_pass_d *make_pass_ipa_icp (gcc::context *ctxt);
|
||
|
|
extern ipa_opt_pass_d *make_pass_ipa_odr (gcc::context *ctxt);
|
||
|
|
extern ipa_opt_pass_d *make_pass_ipa_reference (gcc::context *ctxt);
|
||
|
|
extern ipa_opt_pass_d *make_pass_ipa_pure_const (gcc::context *ctxt);
|
||
|
|
--
|
||
|
|
2.33.0
|
||
|
|
|