Projects
Mega:24.03
gcc
_service:tar_scm:0040-Port-icp-patch-to-GCC-12....
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:tar_scm:0040-Port-icp-patch-to-GCC-12.patch of Package gcc
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
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2