Last year I made modref to track which functions are deterministic - i.e. they produce same effects given same inputs (including memory) and which functions have no side effects (which includes infinite loops, trapping etc.).
deterministic functions can be CSEed just as looping pure/const. These are similar to reproducible/unsequenced attributes. This patch implements tree-ssa-sccvn bits so we can now optimize non-pure function. For example: [[gnu::noinline]] int dup(int *a) { a[0]=a[1]; return a[2]; } int test (int *b) { return dup (b) + dup (b); } Will now be optimized to return 2 * dup (b); The sccvn bits are quite simple: visit_stmt has to allow VN lookups for deterministic functions while visit_reference_op_call currently contains test to give up if function has vdef or no lhs. I think the vdef test was redundant even before since pure/const function can return aggregate. Newly even if there is no lhs we can eliminate redundancies as in: [[gnu::noinline]] void dup(int *a) { a[0]=a[1]; } void test (int *b) { dup (b); dup (b); } I think it is relatively useful feature to optimize out functions that compute some fields of structure based on other fields. It optimizes out 2.6K of clang text segment. This is not much, but I hope it will improve in future. For example, right now dup call can not do sanity checks. Call to abort makes us believe that all global memory is used and that will prevent optimizations. However modref can keep track of the fact that global memory read only happens upon termination and ignore those accesses while determining redundancy... Also reproduce/unsequential makes it possible to ignore stores of function call itself to detemrine redundancy. I am not quite sure how to fit that into PRE. bootstrapped/regtested x86_64-linux. OK? gcc/ChangeLog: * ipa-modref.cc (get_modref_function_summary): Add extra fn paramaeter to pass value numbering info. (ipa_modref_can_remove_redundat_calls): New function. * ipa-modref.h (get_modref_function_summary): Update declaration. (ipa_modref_can_remove_redundat_calls): declare. * tree-ssa-sccvn.cc (visit_reference_op_call): Also try to optimize calls with vdef and no lhs. (visit_stmt): Use ipa_modref_can_remove_redundat_calls. gcc/testsuite/ChangeLog: * gcc.dg/tree-ssa/modref-16.c: New test. * gcc.dg/tree-ssa/modref-17.c: New test. diff --git a/gcc/ipa-modref.cc b/gcc/ipa-modref.cc index 08a7740de94..50d18287219 100644 --- a/gcc/ipa-modref.cc +++ b/gcc/ipa-modref.cc @@ -766,12 +766,14 @@ get_modref_function_summary (cgraph_node *func) /* Get function summary for CALL if it exists, return NULL otherwise. If non-null set interposed to indicate whether function may not bind to current def. In this case sometimes loads from function - needs to be ignored. */ + needs to be ignored. + If FN is non-NULL, it specifies indirect call target (for example when known + to value numbering). */ modref_summary * -get_modref_function_summary (gcall *call, bool *interposed) +get_modref_function_summary (gcall *call, bool *interposed, tree fn) { - tree callee = gimple_call_fndecl (call); + tree callee = fn ? fn : gimple_call_fndecl (call); if (!callee) return NULL; struct cgraph_node *node = cgraph_node::get (callee); @@ -5648,4 +5650,33 @@ ipa_modref_callee_reads_no_memory_p (gcall *call) return false; } +/* Return true if CALL can be optimized out provided that was called with + exactly same value number before. + + If FN is non-NULL, it specifies indirect call target (for example when known + to value numbering). */ + +bool +ipa_modref_can_remove_redundat_calls (gcall *call, tree fn) +{ + if (gimple_call_flags (call) & (ECF_CONST | ECF_PURE)) + return true; + if (fn && (flags_from_decl_or_type (fn) & (ECF_CONST | ECF_PURE))) + return true; + /* If function has multiple ways to return, we would have to remember + the way it returned last time. + Terminating function execution (i.e. throwing external exception) + is OK. */ + basic_block bb = gimple_bb (call); + if (call == gsi_stmt (gsi_last_bb (bb)) && !single_succ_p (bb)) + return false; + modref_summary *sum = get_modref_function_summary (call, NULL, fn); + /* If function is deterministic, we can remove redundant invocations. + However, in current implementaiton, we must track that loads and stores do + not overlap. Special case the situation that function reads and writes + global memory and therefore we know nothing. */ + return sum && !sum->nondeterministic + && (!sum->global_memory_read || !sum->global_memory_written); +} + #include "gt-ipa-modref.h" diff --git a/gcc/ipa-modref.h b/gcc/ipa-modref.h index a0eb63a0afa..5bb4172ef9e 100644 --- a/gcc/ipa-modref.h +++ b/gcc/ipa-modref.h @@ -72,10 +72,12 @@ struct GTY(()) modref_summary }; modref_summary *get_modref_function_summary (cgraph_node *func); -modref_summary *get_modref_function_summary (gcall *call, bool *interposed); +modref_summary *get_modref_function_summary (gcall *call, bool *interposed, + tree fn = NULL); void ipa_modref_cc_finalize (); void ipa_merge_modref_summary_after_inlining (cgraph_edge *e); bool ipa_modref_callee_reads_no_memory_p (gcall *call); +bool ipa_modref_can_remove_redundat_calls (gcall *call, tree fn = NULL); /* All flags that are implied by the ECF_CONST functions. */ static const int implicit_const_eaf_flags diff --git a/gcc/testsuite/gcc.dg/tree-ssa/modref-16.c b/gcc/testsuite/gcc.dg/tree-ssa/modref-16.c new file mode 100644 index 00000000000..702349bbc02 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/modref-16.c @@ -0,0 +1,14 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-release_ssa" } */ +[[gnu::noinline]] +int dup(int *a) +{ + a[0]=a[1]; + return a[2]; +} +int +test (int *b) +{ + return dup (b) + dup (b); +} +/* { dg-final { scan-tree-dump-times "dup .b" 1 "release_ssa"} } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/modref-17.c b/gcc/testsuite/gcc.dg/tree-ssa/modref-17.c new file mode 100644 index 00000000000..703cb46fabb --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/modref-17.c @@ -0,0 +1,13 @@ +/* { dg-options "-O2 -fdump-tree-release_ssa" } */ +[[gnu::noinline]] +void dup(int *a) +{ + a[0]=a[1]; +} +void +test (int *b) +{ + dup (b); + dup (b); +} +/* { dg-final { scan-tree-dump-times "dup .b" 1 "release_ssa"} } */ diff --git a/gcc/tree-ssa-sccvn.cc b/gcc/tree-ssa-sccvn.cc index e93acb44200..6e31f8b3cfe 100644 --- a/gcc/tree-ssa-sccvn.cc +++ b/gcc/tree-ssa-sccvn.cc @@ -5567,8 +5567,6 @@ visit_reference_op_call (tree lhs, gcall *stmt) modref info to find a candidate to CSE to. */ const unsigned accesses_limit = 8; if (!vnresult - && !vdef - && lhs && gimple_vuse (stmt) && (((summary = get_modref_function_summary (stmt, NULL)) && !summary->global_memory_read @@ -6354,19 +6352,18 @@ visit_stmt (gimple *stmt, bool backedges_varying_p = false) /* Pick up flags from a devirtualization target. */ tree fn = gimple_call_fn (stmt); - int extra_fnflags = 0; if (fn && TREE_CODE (fn) == SSA_NAME) { fn = SSA_VAL (fn); if (TREE_CODE (fn) == ADDR_EXPR && TREE_CODE (TREE_OPERAND (fn, 0)) == FUNCTION_DECL) - extra_fnflags = flags_from_decl_or_type (TREE_OPERAND (fn, 0)); + fn = TREE_OPERAND (fn, 0); + else + fn = NULL; } - if ((/* Calls to the same function with the same vuse - and the same operands do not necessarily return the same - value, unless they're pure or const. */ - ((gimple_call_flags (call_stmt) | extra_fnflags) - & (ECF_PURE | ECF_CONST)) + else + fn = NULL; + if ((ipa_modref_can_remove_redundat_calls (call_stmt, fn) /* If calls have a vdef, subsequent calls won't have the same incoming vuse. So, if 2 calls with vdef have the same vuse, we know they're not subsequent.