From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from nikam.ms.mff.cuni.cz (nikam.ms.mff.cuni.cz [195.113.20.16]) by sourceware.org (Postfix) with ESMTPS id CAA9A3857809; Mon, 9 Nov 2020 23:14:19 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.3.2 sourceware.org CAA9A3857809 Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=ucw.cz Authentication-Results: sourceware.org; spf=none smtp.mailfrom=hubicka@kam.mff.cuni.cz Received: by nikam.ms.mff.cuni.cz (Postfix, from userid 16202) id 42EDB28275E; Tue, 10 Nov 2020 00:14:18 +0100 (CET) Date: Tue, 10 Nov 2020 00:14:18 +0100 From: Jan Hubicka To: Richard Biener Cc: gcc@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: Re: Detect EAF flags in ipa-modref Message-ID: <20201109231418.GF65107@kam.mff.cuni.cz> References: <20201108124711.GD65107@kam.mff.cuni.cz> <20201109102946.GA83507@kam.mff.cuni.cz> <20201109131632.GA12394@kam.mff.cuni.cz> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20201109131632.GA12394@kam.mff.cuni.cz> User-Agent: Mutt/1.10.1 (2018-07-13) X-Spam-Status: No, score=-15.5 required=5.0 tests=BAYES_00, GIT_PATCH_0, HEADER_FROM_DIFFERENT_DOMAINS, KAM_DMARC_STATUS, KAM_LAZY_DOMAIN_SECURITY, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.2 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on server2.sourceware.org X-BeenThere: gcc@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 09 Nov 2020 23:14:22 -0000 Hi, this is updated patch for autodetection of EAF flags. Still the goal is to avoid fancy stuff and get besic logic in place (so no dataflow, no IPA propagation, no attempts to handle trickier cases). There is one new failure ./gcc/testsuite/gcc/gcc.sum:FAIL: gcc.dg/sso/t2.c -Wno-scalar-storage-order -O1 -fno-inline output pattern test ./gcc/testsuite/gcc/gcc.sum:FAIL: gcc.dg/sso/t2.c -Wno-scalar-storage-order -O2 output pattern test ./gcc/testsuite/gcc/gcc.sum:FAIL: gcc.dg/sso/t2.c -Wno-scalar-storage-order -Os output pattern test Which I blieve is bug exposed by detecting dump function to be EAF_DIRECT and NOESCAPE (which it is) and packing/updacking the bitfields leads in one bit difference. Still no idea why. Patch seems to be quite effective on cc1plus turning: Alias oracle query stats: refs_may_alias_p: 65808750 disambiguations, 75664890 queries ref_maybe_used_by_call_p: 153485 disambiguations, 66711204 queries call_may_clobber_ref_p: 22816 disambiguations, 28889 queries nonoverlapping_component_refs_p: 0 disambiguations, 36846 queries nonoverlapping_refs_since_match_p: 27271 disambiguations, 58917 must overlaps, 86958 queries aliasing_component_refs_p: 65808 disambiguations, 2067256 queries TBAA oracle: 25929211 disambiguations 60395141 queries 12391384 are in alias set 0 10783783 queries asked about the same object 126 queries asked about the same alias set 0 access volatile 9598698 are dependent in the DAG 1691939 are aritificially in conflict with void * Modref stats: modref use: 14284 disambiguations, 53336 queries modref clobber: 1660281 disambiguations, 2130440 queries 4311165 tbaa queries (2.023603 per modref query) 685304 base compares (0.321673 per modref query) PTA query stats: pt_solution_includes: 959190 disambiguations, 13169678 queries pt_solutions_intersect: 1050969 disambiguations, 13246686 queries Alias oracle query stats: refs_may_alias_p: 66914578 disambiguations, 76692648 queries ref_maybe_used_by_call_p: 244077 disambiguations, 67732086 queries call_may_clobber_ref_p: 111475 disambiguations, 116613 queries nonoverlapping_component_refs_p: 0 disambiguations, 37091 queries nonoverlapping_refs_since_match_p: 27267 disambiguations, 59019 must overlaps, 87056 queries aliasing_component_refs_p: 65870 disambiguations, 2063394 queries TBAA oracle: 26024415 disambiguations 60579490 queries 12450910 are in alias set 0 10806673 queries asked about the same object 125 queries asked about the same alias set 0 access volatile 9605837 are dependent in the DAG 1691530 are aritificially in conflict with void * Modref stats: modref use: 14272 disambiguations, 277680 queries modref clobber: 1669753 disambiguations, 7849135 queries 4330162 tbaa queries (0.551674 per modref query) 699241 base compares (0.089085 per modref query) PTA query stats: pt_solution_includes: 1833920 disambiguations, 13846032 queries pt_solutions_intersect: 1093785 disambiguations, 13309954 queries So almost twice as many pt_solution_includes disambiguations. I will re-run the stats overnight to be sure that it is not independent change (but both build was from almost same checkout). Bootstrapped/regtested x86_64-linux, OK? (I will analyze more the t2.c failure) Honza gcc/ChangeLog: 2020-11-10 Jan Hubicka * gimple.c: Include ipa-modref-tree.h and ipa-modref.h. (gimple_call_arg_flags): Use modref to determine flags. * ipa-modref.c: Include gimple-ssa.h, tree-phinodes.h, tree-ssa-operands.h, stringpool.h and tree-ssanames.h. (analyze_ssa_name_flags): Declare. (modref_summary::useful_p): Summary is also useful if arg flags are known. (dump_eaf_flags): New function. (modref_summary::dump): Use it. (get_modref_function_summary): Be read for current_function_decl being NULL. (memory_access_to): New function. (deref_flags): New function. (call_lhs_flags): New function. (analyze_parms): New function. (analyze_function): Use it. * ipa-modref.h (struct modref_summary): Add arg_flags. gcc/testsuite/ChangeLog: 2020-11-10 Jan Hubicka * gcc.dg/torture/pta-ptrarith-1.c: Escape parametrs. diff --git a/gcc/gimple.c b/gcc/gimple.c index 1afed88e1f1..da90716aa23 100644 --- a/gcc/gimple.c +++ b/gcc/gimple.c @@ -46,6 +46,8 @@ along with GCC; see the file COPYING3. If not see #include "asan.h" #include "langhooks.h" #include "attr-fnspec.h" +#include "ipa-modref-tree.h" +#include "ipa-modref.h" /* All the tuples have their operand vector (if present) at the very bottom @@ -1532,24 +1534,45 @@ int gimple_call_arg_flags (const gcall *stmt, unsigned arg) { attr_fnspec fnspec = gimple_call_fnspec (stmt); - - if (!fnspec.known_p ()) - return 0; - int flags = 0; - if (!fnspec.arg_specified_p (arg)) - ; - else if (!fnspec.arg_used_p (arg)) - flags = EAF_UNUSED; - else + if (fnspec.known_p ()) { - if (fnspec.arg_direct_p (arg)) - flags |= EAF_DIRECT; - if (fnspec.arg_noescape_p (arg)) - flags |= EAF_NOESCAPE; - if (fnspec.arg_readonly_p (arg)) - flags |= EAF_NOCLOBBER; + if (!fnspec.arg_specified_p (arg)) + ; + else if (!fnspec.arg_used_p (arg)) + flags = EAF_UNUSED; + else + { + if (fnspec.arg_direct_p (arg)) + flags |= EAF_DIRECT; + if (fnspec.arg_noescape_p (arg)) + flags |= EAF_NOESCAPE; + if (fnspec.arg_readonly_p (arg)) + flags |= EAF_NOCLOBBER; + } + } + tree callee = gimple_call_fndecl (stmt); + if (callee) + { + cgraph_node *node = cgraph_node::get (callee); + modref_summary *summary = node ? get_modref_function_summary (node) + : NULL; + + if (summary && summary->arg_flags.length () > arg) + { + int modref_flags = summary->arg_flags[arg]; + + /* We have possibly optimized out load. Be conservative here. */ + if (!node->binds_to_current_def_p ()) + { + if ((modref_flags & EAF_UNUSED) && !(flags & EAF_UNUSED)) + modref_flags &= ~EAF_UNUSED; + if ((modref_flags & EAF_DIRECT) && !(flags & EAF_DIRECT)) + modref_flags &= ~EAF_DIRECT; + } + flags |= modref_flags; + } } return flags; } diff --git a/gcc/ipa-modref.c b/gcc/ipa-modref.c index 3f46bebed3c..bde626115ff 100644 --- a/gcc/ipa-modref.c +++ b/gcc/ipa-modref.c @@ -61,6 +61,14 @@ along with GCC; see the file COPYING3. If not see #include "ipa-fnsummary.h" #include "attr-fnspec.h" #include "symtab-clones.h" +#include "gimple-ssa.h" +#include "tree-phinodes.h" +#include "tree-ssa-operands.h" +#include "ssa-iterators.h" +#include "stringpool.h" +#include "tree-ssanames.h" + +static int analyze_ssa_name_flags (tree name, vec *known_flags, int depth); /* We record fnspec specifiers for call edges since they depends on actual gimple statements. */ @@ -186,6 +194,8 @@ modref_summary::useful_p (int ecf_flags) { if (ecf_flags & (ECF_CONST | ECF_NOVOPS)) return false; + if (arg_flags.length ()) + return true; if (loads && !loads->every_base) return true; if (ecf_flags & ECF_PURE) @@ -355,6 +365,22 @@ dump_lto_records (modref_records_lto *tt, FILE *out) } } +/* Dump EAF flags. */ + +static void +dump_eaf_flags (FILE *out, int flags) +{ + if (flags & EAF_DIRECT) + fprintf (out, " direct"); + if (flags & EAF_NOCLOBBER) + fprintf (out, " noclobber"); + if (flags & EAF_NOESCAPE) + fprintf (out, " noescape"); + if (flags & EAF_UNUSED) + fprintf (out, " unused"); + fprintf (out, "\n"); +} + /* Dump summary. */ void @@ -372,6 +398,15 @@ modref_summary::dump (FILE *out) } if (writes_errno) fprintf (out, " Writes errno\n"); + if (arg_flags.length ()) + { + for (unsigned int i = 0; i < arg_flags.length (); i++) + if (arg_flags[i]) + { + fprintf (out, " parm %i flags:", i); + dump_eaf_flags (out, arg_flags[i]); + } + } } /* Dump summary. */ @@ -402,7 +437,8 @@ get_modref_function_summary (cgraph_node *func) function. */ enum availability avail; func = func->function_or_virtual_thunk_symbol - (&avail, cgraph_node::get (current_function_decl)); + (&avail, current_function_decl ? + cgraph_node::get (current_function_decl) : NULL); if (avail <= AVAIL_INTERPOSABLE) return NULL; @@ -1067,6 +1103,315 @@ remove_summary (bool lto, bool nolto, bool ipa) " - modref done with result: not tracked.\n"); } +/* Return true if OP accesses memory pointed to by SSA_NAME. */ + +bool +memory_access_to (tree op, tree ssa_name) +{ + tree base = get_base_address (op); + if (!base) + return false; + if (TREE_CODE (base) != MEM_REF && TREE_CODE (base) != TARGET_MEM_REF) + return false; + return TREE_OPERAND (base, 0) == ssa_name; +} + +/* Consider statement val = *arg. + return EAF flags of ARG that can be determined from EAF flags of VAL + (which are known to be FLAGS). If IGNORE_STORES is true we can ignore + all stores to VAL, i.e. when handling noreturn function. */ + +static int +deref_flags (int flags, bool ignore_stores) +{ + int ret = 0; + if (flags & EAF_UNUSED) + ret |= EAF_DIRECT | EAF_NOCLOBBER | EAF_NOESCAPE; + else + { + if ((flags & EAF_NOCLOBBER) || ignore_stores) + ret |= EAF_NOCLOBBER; + if ((flags & EAF_NOESCAPE) || ignore_stores) + ret |= EAF_NOESCAPE; + } + return ret; +} + +/* Call statements may return their parameters. Consider argument number + ARG of USE_STMT and determine flags that can needs to be cleared + in case pointer possibly indirectly references from ARG I is returned. + KNOWN_FLAGS and DEPTH are same as in analyze_ssa_name_flags. */ + +static int +call_lhs_flags (gimple *use_stmt, int arg, vec *known_flags, int depth) +{ + /* If there is no return value, no flags are affected. */ + if (!gimple_call_lhs (use_stmt)) + return EAF_DIRECT | EAF_NOCLOBBER | EAF_NOESCAPE | EAF_UNUSED; + + /* If we know that function returns given argument and it is not ARG + we can still be happy. */ + int flags = gimple_call_return_flags (as_a (use_stmt)); + if ((flags & ERF_RETURNS_ARG) + && (flags & ERF_RETURN_ARG_MASK) != arg) + return EAF_DIRECT | EAF_NOCLOBBER | EAF_NOESCAPE | EAF_UNUSED; + + /* If return value is SSA name determine its flags. */ + if (TREE_CODE (gimple_call_lhs (use_stmt)) == SSA_NAME) + return analyze_ssa_name_flags + (gimple_call_lhs (use_stmt), known_flags, + depth + 1); + /* In the case of memory store we can do nothing. */ + else + return 0; +} + +/* Analyze EAF flags for SSA name NAME. + KNOWN_FLAGS is a cache for flags we already determined. + DEPTH is a recursion depth used to make debug output prettier. */ + +static int +analyze_ssa_name_flags (tree name, vec *known_flags, int depth) +{ + imm_use_iterator ui; + gimple *use_stmt; + int flags = EAF_DIRECT | EAF_NOCLOBBER | EAF_NOESCAPE | EAF_UNUSED; + + /* See if value is already computed. */ + if ((*known_flags)[SSA_NAME_VERSION (name)]) + { + /* Punt on cycles for now, so we do not need dataflow. */ + if ((*known_flags)[SSA_NAME_VERSION (name)] == 1) + { + if (dump_file) + fprintf (dump_file, + "%*sGiving up on a cycle in SSA graph\n", depth * 4, ""); + return 0; + } + return (*known_flags)[SSA_NAME_VERSION (name)] - 2; + } + /* Recursion guard. */ + (*known_flags)[SSA_NAME_VERSION (name)] = 1; + + if (dump_file) + { + fprintf (dump_file, + "%*sAnalyzing flags of ssa name: ", depth * 4, ""); + print_generic_expr (dump_file, name); + fprintf (dump_file, "\n"); + } + + FOR_EACH_IMM_USE_STMT (use_stmt, ui, name) + { + if (flags == 0) + { + BREAK_FROM_IMM_USE_STMT (ui); + } + if (is_gimple_debug (use_stmt)) + continue; + if (dump_file) + { + fprintf (dump_file, "%*s Analyzing stmt:", depth * 4, ""); + print_gimple_stmt (dump_file, use_stmt, 0); + } + + /* Gimple return may load the return value. */ + if (gimple_code (use_stmt) == GIMPLE_RETURN) + { + if (memory_access_to (gimple_return_retval + (as_a (use_stmt)), name)) + flags &= ~EAF_UNUSED; + } + /* Account for LHS store, arg loads and flags from callee function. */ + else if (is_gimple_call (use_stmt)) + { + tree callee = gimple_call_fndecl (use_stmt); + + /* Recursion would require bit of propagation; give up for now. */ + if (callee && recursive_call_p (current_function_decl, callee)) + flags = 0; + else + { + int ecf_flags = gimple_call_flags (use_stmt); + bool ignore_stores = ignore_stores_p (current_function_decl, + ecf_flags); + + /* Handle *name = func (...). */ + if (gimple_call_lhs (use_stmt) + && memory_access_to (gimple_call_lhs (use_stmt), name)) + flags &= ~(EAF_UNUSED | EAF_NOCLOBBER); + + /* Handle all function parameters. */ + for (unsigned i = 0; i < gimple_call_num_args (use_stmt); i++) + /* Name is directly passed to the callee. */ + if (gimple_call_arg (use_stmt, i) == name) + { + if (ecf_flags & (ECF_CONST | ECF_NOVOPS)) + flags &= ignore_stores + ? 0 + : call_lhs_flags (use_stmt, i, + known_flags, depth); + else + { + int call_flags = gimple_call_arg_flags (as_a + (use_stmt), i); + if (ignore_stores) + call_flags |= EAF_NOCLOBBER | EAF_NOESCAPE; + else + call_flags &= call_lhs_flags (use_stmt, i, + known_flags, depth); + + flags &= call_flags; + } + } + /* Name is dereferenced and passed to a callee. */ + else if (memory_access_to (gimple_call_arg (use_stmt, i), name)) + { + if (ecf_flags & (ECF_CONST | ECF_NOVOPS)) + flags &= ~EAF_UNUSED; + else + flags &= deref_flags (gimple_call_arg_flags + (as_a (use_stmt), i), + ignore_stores); + if (!ignore_stores) + flags &= call_lhs_flags (use_stmt, i, known_flags, + depth); + } + } + /* Only NOCLOBBER or DIRECT flags alone are not useful (see comments + in tree-ssa-alias.c). Give up earlier. */ + if ((flags & ~(EAF_DIRECT | EAF_NOCLOBBER)) == 0) + flags = 0; + } + else if (gimple_assign_load_p (use_stmt)) + { + /* Memory to memory copy. */ + if (gimple_store_p (use_stmt)) + { + /* Handle *name = *exp. */ + if (memory_access_to (gimple_assign_lhs (use_stmt), name)) + flags &= ~(EAF_UNUSED | EAF_NOCLOBBER); + + /* Handle *lhs = *name. + + We do not track memory locations, so assume that value + is used arbitrarily. */ + if (memory_access_to (gimple_assign_rhs1 (use_stmt), name)) + flags = 0; + } + /* Handle lhs = *name. */ + else if (memory_access_to (gimple_assign_rhs1 (use_stmt), name)) + flags &= deref_flags (analyze_ssa_name_flags + (gimple_assign_lhs (use_stmt), + known_flags, depth + 1), false); + } + else if (gimple_store_p (use_stmt)) + { + /* Handle *lhs = name. */ + if (is_gimple_assign (use_stmt) + && gimple_assign_rhs1 (use_stmt) == name) + { + if (dump_file) + fprintf (dump_file, "%*s ssa name saved to memory\n", + depth * 4, ""); + flags = 0; + } + /* Handle *name = exp. */ + else if (is_gimple_assign (use_stmt) + && memory_access_to (gimple_assign_lhs (use_stmt), name)) + flags &= ~(EAF_UNUSED | EAF_NOCLOBBER); + /* ASM statements etc. */ + else if (!is_gimple_assign (use_stmt)) + { + if (dump_file) + fprintf (dump_file, "%*s Unhandled store\n", + depth * 4, ""); + flags = 0; + } + } + else if (is_gimple_assign (use_stmt)) + { + enum tree_code code = gimple_assign_rhs_code (use_stmt); + + /* See if operation is a merge as considered by + tree-ssa-structalias.c:find_func_aliases. */ + if (!truth_value_p (code) + && code != POINTER_DIFF_EXPR + && (code != POINTER_PLUS_EXPR + || gimple_assign_rhs1 (use_stmt) == name)) + flags &= analyze_ssa_name_flags + (gimple_assign_lhs (use_stmt), known_flags, + depth + 1); + } + else if (gimple_code (use_stmt) == GIMPLE_PHI) + { + flags &= analyze_ssa_name_flags + (gimple_phi_result (use_stmt), known_flags, + depth + 1); + } + /* Conditions are not considered escape points + by tree-ssa-structalias. */ + else if (gimple_code (use_stmt) == GIMPLE_COND) + ; + else + { + if (dump_file) + fprintf (dump_file, "%*s Unhandled stmt\n", depth * 4, ""); + flags = 0; + } + + if (dump_file) + { + fprintf (dump_file, "%*s current flags of ", depth * 4, ""); + print_generic_expr (dump_file, name); + dump_eaf_flags (dump_file, flags); + } + } + if (dump_file) + { + fprintf (dump_file, "%*sflags of ssa name ", depth * 4, ""); + print_generic_expr (dump_file, name); + dump_eaf_flags (dump_file, flags); + } + (*known_flags)[SSA_NAME_VERSION (name)] = flags + 2; + return flags; +} + +/* Determine EAF flags for function parameters. */ + +static void +analyze_parms (modref_summary *summary) +{ + unsigned int parm_index = 0; + unsigned int count = 0; + + for (tree parm = DECL_ARGUMENTS (current_function_decl); parm; + parm = TREE_CHAIN (parm)) + count++; + + if (!count) + return; + + auto_vec known_flags; + known_flags.safe_grow_cleared (num_ssa_names); + + for (tree parm = DECL_ARGUMENTS (current_function_decl); parm; parm_index++, + parm = TREE_CHAIN (parm)) + { + tree name = ssa_default_def (cfun, parm); + if (!name) + continue; + int flags = analyze_ssa_name_flags (name, &known_flags, 0); + + if (flags) + { + if (parm_index >= summary->arg_flags.length ()) + summary->arg_flags.safe_grow_cleared (count); + summary->arg_flags[parm_index] = flags; + } + } +} + /* Analyze function F. IPA indicates whether we're running in local mode (false) or the IPA mode (true). */ @@ -1174,6 +1519,10 @@ analyze_function (function *f, bool ipa) param_modref_max_accesses); summary_lto->writes_errno = false; } + + if (!ipa) + analyze_parms (summary); + int ecf_flags = flags_from_decl_or_type (current_function_decl); auto_vec recursive_calls; @@ -1191,8 +1540,9 @@ analyze_function (function *f, bool ipa) || ((!summary || !summary->useful_p (ecf_flags)) && (!summary_lto || !summary_lto->useful_p (ecf_flags)))) { - remove_summary (lto, nolto, ipa); - return; + collapse_loads (summary, summary_lto); + collapse_stores (summary, summary_lto); + break; } } } diff --git a/gcc/ipa-modref.h b/gcc/ipa-modref.h index 31ceffa8d34..8fa05aaf7fb 100644 --- a/gcc/ipa-modref.h +++ b/gcc/ipa-modref.h @@ -29,6 +29,7 @@ struct GTY(()) modref_summary /* Load and stores in function (transitively closed to all callees) */ modref_records *loads; modref_records *stores; + auto_vec GTY((skip)) arg_flags; modref_summary (); ~modref_summary (); diff --git a/gcc/testsuite/gcc.dg/torture/pta-ptrarith-1.c b/gcc/testsuite/gcc.dg/torture/pta-ptrarith-1.c index 99a548840df..85b68068b12 100644 --- a/gcc/testsuite/gcc.dg/torture/pta-ptrarith-1.c +++ b/gcc/testsuite/gcc.dg/torture/pta-ptrarith-1.c @@ -6,11 +6,14 @@ struct Foo { int *p; }; +struct Foo *ff; + void __attribute__((noinline)) foo (void *p) { struct Foo *f = (struct Foo *)p - 1; *f->p = 0; + ff = f; } int bar (void)