This patch enables constant propagation to outlined OpenMP kernels and improves support for optimizing callback functions in general. It implements the attribute 'callback' as found in clang, though argument numbering is a bit different, as described below. The title says OpenMP, but it can be used for any function which takes a callback argument, such as pthread functions, qsort and others.
The attribute 'callback' captures the notion of a function calling one of its arguments with some of its parameters as arguments. An OpenMP example of such function is GOMP_parallel. We implement the attribute with new callgraph edges called 'callback' edges. They are imaginary edges pointing from the caller of the function with the attribute (e.g. caller of GOMP_parallel) to the body function itself (e.g. the outlined OpenMP body). They share their call statement with the edge from which they are derived (direct edge caller -> GOMP_parallel in this case). These edges allow passes such as ipa-cp to the see the hidden call site to the body function and optimize the function accordingly. To illustrate on an example, the body GOMP_parallel looks something like this: void GOMP_parallel (void (*fn) (void *), void *data, /* ... */) { /* ... */ fn (data); /* ... */ } If we extend it with the attribute 'callback(1, 2)', we express that the function calls its first argument and passes it its second argument. This is represented in the call graph in this manner: direct indirect caller -----------------> GOMP_parallel ---------------> fn | ----------------------> fn callback The direct edge is then the parent edge, with all callback edges being the child edges. While constant propagation is the main focus of this patch, callback edges can be useful for different passes (for example, it improves icf for OpenMP kernels), as they allow for address redirection. If the outlined body function gets optimized and cloned, from body_fn to body_fn.optimized, the callback edge allows us to replace the address in the arguments list: GOMP_parallel (body_fn, &data_struct, /* ... */); becomes GOMP_parallel (body_fn.optimized, &data_struct, /* ... */); This redirection is possible for any function with the attribute. This callback attribute implementation is partially compatible with clang's implementation. Its semantics, arguments and argument indexing style are the same, but we represent an unknown argument position with 0 (precedent set by attributes such as 'format'), while clang uses -1 or '?'. We also allow for multiple callback attributes on the same function, while clang only allows one. The attribute allows us to propagate constants into body functions of OpenMP constructs. Currently, GCC won't propagate the value 'c' into the OpenMP body in the following example: int a[100]; void test(int c) { #pragma omp parallel for for (int i = 0; i < c; i++) { if (!__builtin_constant_p(c)) { __builtin_abort(); } a[i] = i; } } int main() { test(100); return a[5] - 5; } With this patch, the body function will get cloned and the constant 'c' will get propagated. Bootstrapped and regtested on x86_64-linux. OK for master? Thanks, Josef Melcr gcc/ChangeLog: * builtin-attrs.def (0): New int list. (ATTR_CALLBACK): Callback attribute identifier. (DEF_CALLBACK_ATTRIBUTE): Macro for callback attribute creation. (GOMP): Attributes for libgomp functions. (OACC): Attribute used for oacc functions. (ATTR_CALLBACK_GOMP_LIST): ATTR_NOTHROW_LIST but with the callback attribute added, used for many libgomp functions. (ATTR_CALLBACK_GOMP_TASK_HELPER_LIST): Helper list for the construction of ATTR_CALLBACK_GOMP_TASK_LIST. (ATTR_CALLBACK_GOMP_TASK_LIST): New attribute list for GOMP_task, includes two callback attributes. (ATTR_CALLBACK_OACC_LIST): Same as ATTR_CALLBACK_GOMP_LIST, used for oacc builtins. * cgraph.cc (cgraph_add_edge_to_call_site_hash): When hashing callback edges, always hash the parent edge. (cgraph_node::get_edge): Always return callback parent edge. (cgraph_edge::set_call_stmt): Add cascade for callback edges. (symbol_table::create_edge): Allow callback edges to share the same call statement. (cgraph_edge::make_callback): New method, derives a callback edge this method is called on. (cgraph_edge::get_callback_parent_edge): New method. (cgraph_edge::first_callback_target): New method. (cgraph_edge::next_callback_target): New method. (cgraph_edge::purge_callback_children): New method. (cgraph_edge::redirect_call_stmt_to_callee): Add callback edge redirection, set call statements for child edges when updating the parent's statement. (cgraph_node::remove_callers): Remove child edges when removing their parent. (cgraph_edge::dump_edge_flags): Add dumping of callback flags. (cgraph_edge::maybe_hot_p): Add exception for callback edges. (cgraph_node::verify_node): Sanity checks for callback edges. * cgraph.h: Add new cgraph_edge flags and a 16 bit hash for identifying which attribute originated which edge. * cgraphclones.cc (cgraph_edge::clone): Copy over callback data. * doc/extend.texi: Add callback attribute documentation. * ipa-cp.cc (purge_useless_callback_edges): New function. (ipcp_decision_stage): Call purge_useless_callback_edges at the end of the decision stage. * ipa-fnsummary.cc (ipa_call_summary_t::duplicate): Add an exception for callback pairs. (analyze_function_body): Copy summary from parent to child, update the child's summary. * ipa-inline-analysis.cc (do_estimate_growth_1): Skip callback edges when estimating growth. * ipa-inline-transform.cc (inline_transform): Redirect callback edges when redirecting their parent. * ipa-inline.cc (can_inline_edge_p): Never inline callback edges. * ipa-param-manipulation.cc (drop_decl_attribute_if_params_changed_p): New function. (ipa_param_adjustments::build_new_function_type): Add new out param, output info about whether args were modified. (ipa_param_adjustments::adjust_decl): Drop callback attr when args are modified. * ipa-param-manipulation.h: Change signature of build_new_function_type. * ipa-prop.cc (ipa_duplicate_jump_function): Add declaration. (init_callback_edge_summary): New function. (ipa_compute_jump_functions_for_edge): Create callback edges. * lto-cgraph.cc (lto_output_edge): Stream out callback data. (input_edge): Input callback data. * omp-builtins.def (BUILT_IN_GOACC_PARALLEL): Use callback attribute. (BUILT_IN_GOMP_PARALLEL_LOOP_STATIC): Likewise. (BUILT_IN_GOMP_PARALLEL_LOOP_GUIDED): Likewise. (BUILT_IN_GOMP_PARALLEL_LOOP_NONMONOTONIC_DYNAMIC): Likewise. (BUILT_IN_GOMP_PARALLEL_LOOP_NONMONOTONIC_RUNTIME): Likewise. (BUILT_IN_GOMP_PARALLEL): Likewise. (BUILT_IN_GOMP_TASK): Likewise. (BUILT_IN_GOMP_PARALLEL_SECTIONS): Likewise. (BUILT_IN_GOMP_TEAMS_REG): Likewise. * tree-core.h (ECF_CB_1_0): New constant for attr callback(1,0). (ECF_CB_1_2): Constant for callback(1,2). (ECF_CB_2_4): Constant for callback(2,4). (ECF_CB_3_0_2): Constant for callback(3,0,2). * tree-inline.cc (copy_bb): Copy callback edges when copying their parent. (redirect_all_calls): Redirect callback edges. * tree.cc (set_call_expr_flags): Create callback attributes according to the ECF_CB constants. * attr-callback.h: New file. gcc/c-family/ChangeLog: * c-attribs.cc: Add callback attribute definition. gcc/fortran/ChangeLog: * f95-lang.cc (ATTR_CALLBACK_GOMP_LIST): New attr list corresponding to the definition in builtin-attrs. (ATTR_CALLBACK_GOMP_TASK_LIST): Likewise. (ATTR_CALLBACK_OACC_LIST): Likewise. gcc/testsuite/ChangeLog: * gcc.dg/attr-callback.c: New test. * gcc.dg/ipa/ipcp-cb1.c: New test. * gcc.dg/ipa/ipcp-cb2.c: New test. Signed-off-by: Josef Melcr <melcr...@fit.cvut.cz> --- gcc/attr-callback.h | 322 +++++++++++++++++++++++++++ gcc/builtin-attrs.def | 21 ++ gcc/c-family/c-attribs.cc | 3 + gcc/cgraph.cc | 266 +++++++++++++++++++++- gcc/cgraph.h | 42 ++++ gcc/cgraphclones.cc | 3 + gcc/doc/extend.texi | 37 +++ gcc/fortran/f95-lang.cc | 4 + gcc/ipa-cp.cc | 69 +++++- gcc/ipa-fnsummary.cc | 24 +- gcc/ipa-inline-analysis.cc | 5 + gcc/ipa-inline-transform.cc | 12 +- gcc/ipa-inline.cc | 5 + gcc/ipa-param-manipulation.cc | 36 ++- gcc/ipa-param-manipulation.h | 2 +- gcc/ipa-prop.cc | 86 ++++++- gcc/lto-cgraph.cc | 6 + gcc/omp-builtins.def | 28 +-- gcc/testsuite/gcc.dg/attr-callback.c | 79 +++++++ gcc/testsuite/gcc.dg/ipa/ipcp-cb1.c | 25 +++ gcc/testsuite/gcc.dg/ipa/ipcp-cb2.c | 53 +++++ gcc/tree-core.h | 14 ++ gcc/tree-inline.cc | 27 ++- gcc/tree.cc | 42 ++++ 24 files changed, 1176 insertions(+), 35 deletions(-) create mode 100644 gcc/attr-callback.h create mode 100644 gcc/testsuite/gcc.dg/attr-callback.c create mode 100644 gcc/testsuite/gcc.dg/ipa/ipcp-cb1.c create mode 100644 gcc/testsuite/gcc.dg/ipa/ipcp-cb2.c diff --git a/gcc/attr-callback.h b/gcc/attr-callback.h new file mode 100644 index 00000000000..19abbdd09ed --- /dev/null +++ b/gcc/attr-callback.h @@ -0,0 +1,322 @@ +/* Callback attribute handling + Copyright (C) 2025 Free Software Foundation, Inc. + Contributed by Josef Melcr <melcr...@fit.cvut.cz> + + This file is part of GCC. + + GCC is free software; you can redistribute it and/or modify + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + GCC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GCC; see the file COPYING3. If not see + <http://www.gnu.org/licenses/>. */ + +#ifndef ATTR_CALLBACK_H +#define ATTR_CALLBACK_H +#include "attribs.h" +#include "cgraph.h" +#include "system.h" +#include "tree.h" +#include "function.h" +#include "basic-block.h" +#include "coretypes.h" +#include "is-a.h" +#include "predict.h" +#include "internal-fn.h" +#include "tree-ssa-alias.h" +#include "gimple-expr.h" +#include "gimple.h" +#include "vec.h" +#include "inchash.h" + +enum callback_position +{ + /* Value used when an argument of a callback function + is unknown or when multiple values may be used. */ + CB_UNKNOWN_POS = 0 +}; + +/* Given an instance of callback attribute, return the 0-based + index of the called function in question. */ +inline int +callback_get_fn_index (tree cb_attr) +{ + tree args = TREE_VALUE (cb_attr); + int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1; + return idx; +} + +/* Calculates the incremental hash of the attributes arguments, narrowed down to + 16 bits. */ +inline unsigned int +callback_hash_attr (tree attr) +{ + inchash::hash hasher; + tree it; + for (it = TREE_VALUE (attr); it != NULL_TREE; it = TREE_CHAIN (it)) + { + unsigned int val = (unsigned int) TREE_INT_CST_LOW (TREE_VALUE (it)); + hasher.add_int (val); + } + unsigned int hash = hasher.end (); + hash &= 0xffff; + return hash; +} + +/* For a given callback parent-child pair, retrieves the callback attribute used + * to create E from the callee of PARENT. */ +inline tree +callback_fetch_attr_by_edge (cgraph_edge *e, cgraph_edge *parent) +{ + gcc_checking_assert (e->call_stmt == parent->call_stmt + && e->lto_stmt_uid == parent->lto_stmt_uid); + tree cb_attr + = lookup_attribute ("callback", DECL_ATTRIBUTES (parent->callee->decl)); + gcc_checking_assert (cb_attr); + tree res = NULL_TREE; + for (; cb_attr; cb_attr = lookup_attribute ("callback", TREE_CHAIN (cb_attr))) + { + unsigned hash = callback_hash_attr (cb_attr); + if (hash == e->callback_hash) + { + res = cb_attr; + break; + } + } + gcc_checking_assert (res != NULL_TREE); + return res; +} + +/* Given an instance of callback attribute, return the 0-base indices + of arguments passed to the callback. For a callback function taking + n parameters, returns a vector of n indices of their values in the parameter + list of it's caller. Indices with unknown positions will be filled with + an identity. */ +inline auto_vec<int> +callback_get_arg_mapping (cgraph_edge *e, cgraph_edge *parent) +{ + tree attr = callback_fetch_attr_by_edge(e, parent); + gcc_checking_assert (attr); + tree args = TREE_VALUE (attr); + auto_vec<int> res; + tree it; + + /* Skip over the first argument, which denotes + which argument is the called function. */ + for (it = TREE_CHAIN (args); it != NULL_TREE; it = TREE_CHAIN (it)) + { + int idx = TREE_INT_CST_LOW (TREE_VALUE (it)); + + /* CB_UNKNOWN_POS signifies an unknown argument, + replace it with identity for convenience */ + if (idx == CB_UNKNOWN_POS) + idx = res.length (); + /* arguments use 1-based indexing, so we have + to subtract 1 */ + else + idx -= 1; + + res.safe_push (idx); + } + + return res; +} + +/* For a callback parent-child pair, returns the 0-based index of the address of + E's callee in the argument list of PARENT's callee decl. */ +inline int +callback_fetch_fn_position (cgraph_edge *e, cgraph_edge *parent) +{ + tree attr = callback_fetch_attr_by_edge (e, parent); + return callback_get_fn_index (attr); +} + +/* Returns the element at index idx in the list or NULL_TREE if + the list isn't long enough. NULL_TREE is used as the endpoint. */ +static tree +get_nth_list_elem (tree list, unsigned idx) +{ + tree res = NULL_TREE; + unsigned i = 0; + tree it; + for (it = list; it != NULL_TREE; it = TREE_CHAIN (it), i++) + { + if (i == idx) + { + res = TREE_VALUE (it); + break; + } + } + return res; +} + +/* Handle a "callback" attribute; arguments as in + struct attribute_spec.handler. */ +inline tree +handle_callback_attribute (tree *node, tree name, tree args, + int ARG_UNUSED (flags), bool *no_add_attrs) +{ + tree decl = *node; + if (TREE_CODE (decl) != FUNCTION_DECL) + { + error_at (DECL_SOURCE_LOCATION (decl), + "%qE attribute can only be used on functions", name); + *no_add_attrs = true; + } + + tree cb_fn_idx_node = TREE_VALUE (args); + if (TREE_CODE (cb_fn_idx_node) != INTEGER_CST) + { + error_at (DECL_SOURCE_LOCATION (decl), + "argument specifying callback function position is not an " + "integer constant"); + *no_add_attrs = true; + return NULL_TREE; + } + /* We have to use the function type for validation, as + DECL_ARGUMENTS returns NULL at this point. */ + unsigned callback_fn_idx = TREE_INT_CST_LOW (cb_fn_idx_node); + tree decl_type_args = TYPE_ARG_TYPES (TREE_TYPE (decl)); + tree it; + unsigned decl_nargs = list_length (decl_type_args); + for (it = decl_type_args; it != NULL_TREE; it = TREE_CHAIN (it)) + if (it == void_list_node) + { + --decl_nargs; + break; + } + if (callback_fn_idx == CB_UNKNOWN_POS) + { + error_at (DECL_SOURCE_LOCATION (decl), + "callback function position cannot be marked as unknown"); + *no_add_attrs = true; + return NULL_TREE; + } + --callback_fn_idx; + if (callback_fn_idx >= decl_nargs) + { + error_at (DECL_SOURCE_LOCATION (decl), + "callback function position out of range"); + *no_add_attrs = true; + return NULL_TREE; + } + + /* Search for the type of the callback function + in parameters of the original function. */ + tree cfn = get_nth_list_elem(decl_type_args, callback_fn_idx); + if (cfn == NULL_TREE) + { + error_at (DECL_SOURCE_LOCATION (decl), + "could not retrieve callback function from arguments"); + *no_add_attrs = true; + return NULL_TREE; + } + tree cfn_pointee_type = TREE_TYPE (cfn); + if (TREE_CODE (cfn) != POINTER_TYPE + || TREE_CODE (cfn_pointee_type) != FUNCTION_TYPE) + { + error_at (DECL_SOURCE_LOCATION (decl), + "argument no. %d is not an address of a function", + callback_fn_idx + 1); + *no_add_attrs = true; + return NULL_TREE; + } + + tree type_args = TYPE_ARG_TYPES (cfn_pointee_type); + /* Compare the length of the list of argument indices + and the real number of parameters the callback takes. */ + unsigned cfn_nargs = list_length (TREE_CHAIN (args)); + unsigned type_nargs = list_length (type_args); + for (it = type_args; it != NULL_TREE; it = TREE_CHAIN (it)) + if (it == void_list_node) + { + --type_nargs; + break; + } + if (cfn_nargs != type_nargs) + { + error_at (DECL_SOURCE_LOCATION (decl), + "argument number mismatch, %d expected, got %d", type_nargs, + cfn_nargs); + *no_add_attrs = true; + return NULL_TREE; + } + + unsigned curr = 0; + tree cfn_it; + /* Validate type compatibility of the arguments passed + from caller function to callback. "it" is used to step + through the parameters of the caller, "cfn_it" is + stepping through the parameters of the callback. */ + for (it = type_args, cfn_it = TREE_CHAIN (args); curr < type_nargs; + it = TREE_CHAIN (it), cfn_it = TREE_CHAIN (cfn_it), curr++) + { + if (TREE_CODE (TREE_VALUE (cfn_it)) != INTEGER_CST) + { + error_at (DECL_SOURCE_LOCATION (decl), + "argument no. %d is not an integer constant", curr + 1); + *no_add_attrs = true; + continue; + } + + unsigned arg_idx = TREE_INT_CST_LOW (TREE_VALUE (cfn_it)); + + /* No need to check for type compatibility, + if we don't know what we are passing. */ + if (arg_idx == CB_UNKNOWN_POS) + { + continue; + } + + arg_idx -= 1; + /* Report an error if the position is out of bounds, + but we can still check the rest of the arguments. */ + if (arg_idx >= decl_nargs) + { + error_at (DECL_SOURCE_LOCATION (decl), + "callback argument index %d is out of range", arg_idx + 1); + *no_add_attrs = true; + continue; + } + + tree arg_type = get_nth_list_elem (decl_type_args, arg_idx); + tree expected_type = TREE_VALUE (it); + /* Check the type of the value we are about to pass ("arg_type") + for compatibility with the actual type the callback function + expects ("expected_type"). */ + if (!types_compatible_p (expected_type, arg_type)) + { + error_at (DECL_SOURCE_LOCATION (decl), + "argument type at index %d is not compatible with callback " + "argument type at index %d", + arg_idx + 1, curr + 1); + *no_add_attrs = true; + continue; + } + } + + return NULL_TREE; +} + +/* Returns TRUE if E is considered useful in the callgraph, FALSE otherwise. If + * this predicate returns FALSE, then E wasn't used to optimize its callee and + * can be safely removed from the callgraph. */ +inline bool +callback_edge_useful_p (cgraph_edge *e) +{ + gcc_checking_assert (e->callback); + /* If the edge is not pointing towards a clone, it is no longer useful as its + entire purpose is to produce clones of callbacks. */ + if (!e->callee->clone_of) + return false; + return true; +} + +#endif /* ATTR_CALLBACK_H */ diff --git a/gcc/builtin-attrs.def b/gcc/builtin-attrs.def index 850efea11ca..f6043747773 100644 --- a/gcc/builtin-attrs.def +++ b/gcc/builtin-attrs.def @@ -75,6 +75,7 @@ DEF_ATTR_FOR_STRING (STRERRNOP, ".P") #define DEF_LIST_INT_INT(VALUE1, VALUE2) \ DEF_ATTR_TREE_LIST (ATTR_LIST_##VALUE1##_##VALUE2, ATTR_NULL, \ ATTR_##VALUE1, ATTR_LIST_##VALUE2) +DEF_LIST_INT_INT (0,2) DEF_LIST_INT_INT (1,0) DEF_LIST_INT_INT (1,2) DEF_LIST_INT_INT (1,3) @@ -122,6 +123,7 @@ DEF_ATTR_IDENT (ATTR_TM_TMPURE, "transaction_pure") DEF_ATTR_IDENT (ATTR_RETURNS_TWICE, "returns_twice") DEF_ATTR_IDENT (ATTR_RETURNS_NONNULL, "returns_nonnull") DEF_ATTR_IDENT (ATTR_WARN_UNUSED_RESULT, "warn_unused_result") +DEF_ATTR_IDENT (ATTR_CALLBACK, "callback") DEF_ATTR_TREE_LIST (ATTR_NOVOPS_LIST, ATTR_NOVOPS, ATTR_NULL, ATTR_NULL) @@ -416,6 +418,25 @@ DEF_FORMAT_ATTRIBUTE_NOTHROW(STRFMON,3,3_4) #undef DEF_FORMAT_ATTRIBUTE_NOTHROW #undef DEF_FORMAT_ATTRIBUTE_BOTH +/* Callback attr */ +#define DEF_CALLBACK_ATTRIBUTE(TYPE, CA, VALUES) \ + DEF_ATTR_TREE_LIST (ATTR_CALLBACK_##TYPE##_##CA##_##VALUES, ATTR_CALLBACK,\ + ATTR_##CA, ATTR_LIST_##VALUES) + +DEF_CALLBACK_ATTRIBUTE(GOMP, 1, 0) +DEF_CALLBACK_ATTRIBUTE(GOMP, 1, 2) +DEF_CALLBACK_ATTRIBUTE(OACC, 2, 4) +DEF_CALLBACK_ATTRIBUTE(GOMP, 3, 0_2) +DEF_ATTR_TREE_LIST(ATTR_CALLBACK_GOMP_LIST, ATTR_CALLBACK, + ATTR_CALLBACK_GOMP_1_2, ATTR_NOTHROW_LIST) +DEF_ATTR_TREE_LIST(ATTR_CALLBACK_GOMP_TASK_HELPER_LIST, ATTR_CALLBACK, + ATTR_CALLBACK_GOMP_1_0, ATTR_NOTHROW_LIST) +DEF_ATTR_TREE_LIST(ATTR_CALLBACK_GOMP_TASK_LIST, ATTR_CALLBACK, + ATTR_CALLBACK_GOMP_3_0_2, ATTR_CALLBACK_GOMP_TASK_HELPER_LIST) +DEF_ATTR_TREE_LIST(ATTR_CALLBACK_OACC_LIST, ATTR_CALLBACK, + ATTR_CALLBACK_OACC_2_4, ATTR_NOTHROW_LIST) +#undef DEF_CALLBACK_ATTRIBUTE + /* Transactional memory variants of the above. */ DEF_ATTR_TREE_LIST (ATTR_TM_NOTHROW_LIST, diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc index 5a0e3d328ba..d88faf69544 100644 --- a/gcc/c-family/c-attribs.cc +++ b/gcc/c-family/c-attribs.cc @@ -49,6 +49,7 @@ along with GCC; see the file COPYING3. If not see #include "tree-pretty-print.h" #include "gcc-rich-location.h" #include "gcc-urlifier.h" +#include "attr-callback.h" static tree handle_packed_attribute (tree *, tree, tree, int, bool *); static tree handle_nocommon_attribute (tree *, tree, tree, int, bool *); @@ -465,6 +466,8 @@ const struct attribute_spec c_common_gnu_attributes[] = handle_tm_attribute, NULL }, { "transaction_may_cancel_outer", 0, 0, false, true, false, false, handle_tm_attribute, NULL }, + { "callback", 1, -1, true, false, false, false, + handle_callback_attribute, NULL}, /* ??? These two attributes didn't make the transition from the Intel language document to the multi-vendor language document. */ { "transaction_pure", 0, 0, false, true, false, false, diff --git a/gcc/cgraph.cc b/gcc/cgraph.cc index 6ae6a97f6f5..ee8ebe04e73 100644 --- a/gcc/cgraph.cc +++ b/gcc/cgraph.cc @@ -69,6 +69,7 @@ along with GCC; see the file COPYING3. If not see #include "tree-nested.h" #include "symtab-thunks.h" #include "symtab-clones.h" +#include "attr-callback.h" /* FIXME: Only for PROP_loops, but cgraph shouldn't have to know about this. */ #include "tree-pass.h" @@ -720,11 +721,21 @@ cgraph_add_edge_to_call_site_hash (cgraph_edge *e) one indirect); always hash the direct one. */ if (e->speculative && e->indirect_unknown_callee) return; + /* We always want to hash the parent edge of a callback, not the edges + pointing to the callbacks themselves, as their call statement doesn't + exist. */ + if (e->callback) + return; cgraph_edge **slot = e->caller->call_site_hash->find_slot_with_hash (e->call_stmt, cgraph_edge_hasher::hash (e->call_stmt), INSERT); if (*slot) { - gcc_assert (((cgraph_edge *)*slot)->speculative); + cgraph_edge *edge = (cgraph_edge *) *slot; + gcc_assert (edge->speculative || edge->has_callback); + if (edge->has_callback) + /* If the slot is already occupied, then the hashed edge is the parent, + which is desired behavior, so we can safely return. */ + return; if (e->callee && (!e->prev_callee || !e->prev_callee->speculative || e->prev_callee->call_stmt != e->call_stmt)) @@ -768,6 +779,13 @@ cgraph_node::get_edge (gimple *call_stmt) n++; } + /* We want to work with the parent edge whenever possible. When it comes to + callback edges, a call statement might have multiple callback edges + attached to it. These can be easily obtained from the parent edge instead. + */ + if (e && e->callback) + e = e->get_callback_parent_edge (); + if (n > 100) { call_site_hash = hash_table<cgraph_edge_hasher>::create_ggc (120); @@ -837,8 +855,31 @@ cgraph_edge::set_call_stmt (cgraph_edge *e, gcall *new_stmt, return e_indirect ? indirect : direct; } - if (new_direct_callee) - e = make_direct (e, new_direct_callee); + /* Callback edges also need their call stmts changed. + We can use the same flag as for speculative edges. */ + if (update_speculative && (e->callback || e->has_callback)) + { + cgraph_edge *current, *next; + + current = e->first_callback_target (); + if (current) + { + gcall *old_stmt = current->call_stmt; + for (cgraph_edge *d = current; d; d = next) + { + next = d->next_callee; + for (; next; next = next->next_callee) + { + /* has_callback doesn't need to checked, as their + call statements wouldn't match */ + if (next->callback && old_stmt == next->call_stmt) + break; + } + cgraph_edge *d2 = set_call_stmt (d, new_stmt, false); + gcc_assert (d2 == d); + } + } + } /* Only direct speculative edges go to call_site_hash. */ if (e->caller->call_site_hash @@ -885,7 +926,7 @@ symbol_table::create_edge (cgraph_node *caller, cgraph_node *callee, construction of call stmt hashtable. */ cgraph_edge *e; gcc_checking_assert (!(e = caller->get_edge (call_stmt)) - || e->speculative); + || e->speculative || e->has_callback || e->callback); gcc_assert (is_gimple_call (call_stmt)); } @@ -912,6 +953,9 @@ symbol_table::create_edge (cgraph_node *caller, cgraph_node *callee, edge->indirect_info = NULL; edge->indirect_inlining_edge = 0; edge->speculative = false; + edge->has_callback = false; + edge->callback = false; + edge->callback_hash = 0; edge->indirect_unknown_callee = indir_unknown_callee; if (call_stmt && caller->call_site_hash) cgraph_add_edge_to_call_site_hash (edge); @@ -1135,6 +1179,117 @@ cgraph_edge::make_speculative (cgraph_node *n2, profile_count direct_count, return e2; } +/* Turn edge into a callback edge calling N2. Callback edges + never get turned into actual calls, they are just used + as clues and allow for optimizing functions which do not + have any callsites during compile time, e.g. functions + passed to standard library functions. + + The edge will be attached to the same call statement as + it's parent, which is the instance this method is called on. + + callback_hash is used to pair the returned edge with the attribute that + originated it. + + Return the resulting callback edge. */ + +cgraph_edge * +cgraph_edge::make_callback (cgraph_node *n2, unsigned int callback_hash) +{ + cgraph_node *n = caller; + cgraph_edge *e2; + + has_callback = true; + e2 = n->create_edge (n2, call_stmt, count); + if (dump_file) + fprintf (dump_file, + "Created callback edge %s -> %s belonging to parent %s -> %s\n", + e2->caller->dump_name (), e2->callee->dump_name (), + caller->name (), callee->name ()); + initialize_inline_failed (e2); + e2->callback = true; + e2->callback_hash = callback_hash; + if (TREE_NOTHROW (n2->decl)) + e2->can_throw_external = false; + else + e2->can_throw_external = can_throw_external; + e2->lto_stmt_uid = lto_stmt_uid; + n2->mark_address_taken (); + return e2; +} + +/* Returns the parent edge of a callback edge on which + it is called on or NULL when no such edge can be found. + + An edge is taken to be a parent if it has it's has_callback + flag set and the edges share their call statements. */ + +cgraph_edge * +cgraph_edge::get_callback_parent_edge () +{ + gcc_checking_assert (callback); + cgraph_edge *e; + for (e = caller->callees; e; e = e->next_callee) + { + if (e->has_callback && e->call_stmt == call_stmt + && e->lto_stmt_uid == lto_stmt_uid) + break; + } + return e; +} + +/* Returns the first callback edge in the list of callees of the caller node. + Note that the edges might be in arbitrary order. Must be called on a + callback or parent edge. */ +cgraph_edge * +cgraph_edge::first_callback_target () +{ + gcc_checking_assert (has_callback || callback); + cgraph_edge *e = NULL; + for (e = caller->callees; e; e = e->next_callee) + { + if (e->callback && e->call_stmt == call_stmt + && e->lto_stmt_uid == lto_stmt_uid) + { + break; + } + } + return e; +} + +/* Given a callback edge, returns the next callback edge belonging to the same + parent. Must be called on a callback edge, not the parent.*/ +cgraph_edge * +cgraph_edge::next_callback_target () +{ + gcc_checking_assert (callback); + cgraph_edge *e = NULL; + for (e = next_callee; e; e = e->next_callee) + { + if (e->callback && e->call_stmt == call_stmt + && e->lto_stmt_uid == lto_stmt_uid) + { + break; + } + } + return e; +} + +/* When called on a callback parent edge, removes all of its child edges and + sets has_callback to FALSE. */ +void +cgraph_edge::purge_callback_children () +{ + gcc_checking_assert (has_callback); + cgraph_edge *e, *next; + for (e = first_callback_target (); e; e = next) + { + next = e->next_callback_target (); + cgraph_edge::remove (e); + } + has_callback = false; +} + /* Speculative call consists of an indirect edge and one or more direct edge+ref pairs. @@ -1494,6 +1649,24 @@ cgraph_edge::redirect_call_stmt_to_callee (cgraph_edge *e, || decl == e->callee->decl) return e->call_stmt; + /* When redirecting a callback edge, all we need to do is replace + the original address with the address of the function we are + redirecting to. */ + if (e->callback) + { + cgraph_edge *parent = e->get_callback_parent_edge (); + if (!lookup_attribute ("callback", + DECL_ATTRIBUTES (parent->callee->decl))) + /* Callback attribute is removed if the offloading function changes + signature, as the indices would be correct anymore. These edges will + get cleaned up later, ignore their redirection for now. */ + return e->call_stmt; + int fn_idx + = callback_fetch_fn_position (e, parent); + gimple_call_set_arg (e->call_stmt, fn_idx, build_addr (e->callee->decl)); + return e->call_stmt; + } + if (decl && ipa_saved_clone_sources) { tree *p = ipa_saved_clone_sources->get (e->callee); @@ -1603,7 +1776,9 @@ cgraph_edge::redirect_call_stmt_to_callee (cgraph_edge *e, maybe_remove_unused_call_args (DECL_STRUCT_FUNCTION (e->caller->decl), new_stmt); - e->caller->set_call_stmt_including_clones (e->call_stmt, new_stmt, false); + /* Update callback child edges if setting the parent's statement, or else + their their pairing would fall apart. */ + e->caller->set_call_stmt_including_clones (e->call_stmt, new_stmt, e->has_callback); if (symtab->dump_file) { @@ -1782,6 +1957,18 @@ cgraph_node::remove_callers (void) for (e = callers; e; e = f) { f = e->next_caller; + /* When removing a parent edge, remove all its child edges as well. */ + if (e->has_callback) + { + cgraph_edge *cbe, *next_cbe = NULL; + for (cbe = e->first_callback_target (); cbe; cbe = next_cbe) + { + next_cbe = cbe->next_callback_target (); + symtab->call_edge_removal_hooks (cbe); + cbe->remove_caller (); + symtab->free_edge (cbe); + } + } symtab->call_edge_removal_hooks (e); e->remove_caller (); symtab->free_edge (e); @@ -2091,6 +2278,10 @@ cgraph_edge::dump_edge_flags (FILE *f) { if (speculative) fprintf (f, "(speculative) "); + if (callback) + fprintf (f, "(callback) "); + if (has_callback) + fprintf (f, "(has_callback) "); if (!inline_failed) fprintf (f, "(inlined) "); if (call_stmt_cannot_inline_p) @@ -2989,6 +3180,10 @@ cgraph_edge::cannot_lead_to_return_p (void) bool cgraph_edge::maybe_hot_p (void) { + /* TODO: Always consider callback hot, otherwise they would never get cloned. + This can be changed after ipa-cp heuristics get fixed. */ + if (callback) + return true; if (!maybe_hot_count_p (NULL, count.ipa ())) return false; if (caller->frequency == NODE_FREQUENCY_UNLIKELY_EXECUTED @@ -3656,6 +3851,8 @@ cgraph_node::verify_node (void) if (gimple_has_body_p (e->caller->decl) && !e->caller->inlined_to && !e->speculative + && !e->callback + && !e->has_callback /* Optimized out calls are redirected to __builtin_unreachable. */ && (e->count.nonzero_p () || ! e->callee->decl @@ -3861,7 +4058,12 @@ cgraph_node::verify_node (void) } if (!e->indirect_unknown_callee) { - if (e->verify_corresponds_to_fndecl (decl)) + /* Callback edges violate this assertion + because their call statement doesn't exist, + their associated statement belongs to the + offloading function. */ + if (!e->callback + && e->verify_corresponds_to_fndecl (decl)) { error ("edge points to wrong declaration:"); debug_tree (e->callee->decl); @@ -3903,7 +4105,57 @@ cgraph_node::verify_node (void) for (e = callees; e; e = e->next_callee) { - if (!e->aux && !e->speculative) + if (!e->callback && e->callback_hash) + { + error ("non-callback edge has callback_hash set"); + error_found = true; + } + + if (e->callback && e->has_callback) + { + error ("edge has both callback and has_callback set"); + error_found = true; + } + + if (e->callback) + { + if (!e->get_callback_parent_edge ()) + { + error ("callback edge %s->%s has no parent", + identifier_to_locale (e->caller->name ()), + identifier_to_locale (e->callee->name ())); + error_found = true; + } + } + + if (e->has_callback) + { + int ncallbacks = 0; + int nfound_edges = 0; + for (tree cb = lookup_attribute ("callback", DECL_ATTRIBUTES ( + e->callee->decl)); + cb; cb = lookup_attribute ("callback", TREE_CHAIN (cb)), + ncallbacks++) + ; + for (cgraph_edge *cbe = callees; cbe; cbe = cbe->next_callee) + { + if (cbe->callback && cbe->call_stmt == e->call_stmt + && cbe->lto_stmt_uid == e->lto_stmt_uid) + { + nfound_edges++; + } + } + if (ncallbacks < nfound_edges) + { + error ("callback edge %s->%s child edge count mismatch, " + "expected at most %d, found %d", + identifier_to_locale (e->caller->name ()), + identifier_to_locale (e->callee->name ()), ncallbacks, + nfound_edges); + } + } + + if (!e->aux && !e->speculative && !e->callback && !e->has_callback) { error ("edge %s->%s has no corresponding call_stmt", identifier_to_locale (e->caller->name ()), diff --git a/gcc/cgraph.h b/gcc/cgraph.h index abde770ba2b..cc12ed0c97c 100644 --- a/gcc/cgraph.h +++ b/gcc/cgraph.h @@ -1736,6 +1736,31 @@ public: cgraph_edge *make_speculative (cgraph_node *n2, profile_count direct_count, unsigned int speculative_id = 0); + /* Turns edge into a callback edge, representing an indirect call to n2 + passed to a function by argument. Sets has_callback flag of the original + edge. Both edges are attached to the same call statement. Returns created + callback edge. */ + cgraph_edge *make_callback (cgraph_node *n2, unsigned int callback_hash); + + /* Returns the parent edge of a callback edge or NULL, if such edge + cannot be found. An edge is considered a parent, if it has it's + has_callback flag set and shares it's call statement with the edge + this method is caled on. */ + cgraph_edge *get_callback_parent_edge (); + + /* Returns the first callback edge in the list of callees of the caller node. + Note that the edges might be in arbitrary order. Must be called on a + callback or parent edge. */ + cgraph_edge *first_callback_target (); + + /* Given a callback edge, returns the next callback edge belonging to the same + parent. Must be called on a callback edge, not the parent.*/ + cgraph_edge *next_callback_target (); + + /* When called on a callback parent edge, removes all of its child edges and + sets has_callback to FALSE. */ + void purge_callback_children (); + /* Speculative call consists of an indirect edge and one or more direct edge+ref pairs. Speculative will expand to the following sequence: @@ -1952,6 +1977,23 @@ public: Optimizers may later redirect direct call to clone, so 1) and 3) do not need to necessarily agree with destination. */ unsigned int speculative : 1; + /* Edges with CALLBACK flag represent indirect calls to functions passed + to their callers by argument. This is useful in cases, where the body + of these caller functions is not known, e. g. qsort in glibc or + GOMP_parallel in libgomp. These edges are never made into real calls, + but are used instead to optimize these callback functions and later replace + their addresses with their optimized versions. Edges with this flag set + share their call statement with their parent edge. */ + unsigned int callback : 1; + /* Edges with this flag set have one or more child callabck edges. They share + their call statements with this edge. This flag represents the fact that + the callee of this edge takes a function and it's parameters by argument + and calls it at a later time. */ + unsigned int has_callback : 1; + /* Hash calculated from arguments of a callback attribute. Used to pair + callback edges and the attributes that originated them together. Needed + in order to get ipa-icf to work with callbacks. */ + unsigned int callback_hash : 16; /* Set to true when caller is a constructor or destructor of polymorphic type. */ unsigned in_polymorphic_cdtor : 1; diff --git a/gcc/cgraphclones.cc b/gcc/cgraphclones.cc index e6223fa1f5c..8063ba77536 100644 --- a/gcc/cgraphclones.cc +++ b/gcc/cgraphclones.cc @@ -144,6 +144,9 @@ cgraph_edge::clone (cgraph_node *n, gcall *call_stmt, unsigned stmt_uid, new_edge->can_throw_external = can_throw_external; new_edge->call_stmt_cannot_inline_p = call_stmt_cannot_inline_p; new_edge->speculative = speculative; + new_edge->callback = callback; + new_edge->has_callback = has_callback; + new_edge->callback_hash = callback_hash; new_edge->in_polymorphic_cdtor = in_polymorphic_cdtor; /* Update IPA profile. Local profiles need no updating in original. */ diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index 0978c4c41b2..f23fe11d9fd 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -1970,6 +1970,43 @@ declares that @code{my_alloc1} returns 16-byte aligned pointers and that @code{my_alloc2} returns a pointer whose value modulo 32 is equal to 8. +@cindex @code{callback} function attribute +@item callback (@var{function-pos}, @var{...}) +The @code{callback} attribute specifies that a function takes a pointer to +a callback function as one of it's parameters and passes it some of it's own +parameters. For example: + +@smallexample +void foo(void (*fn)(int*), int *data) __attribute__((callback(1, 2))); +@end smallexample + +where body of @code{foo} looks something like: + +@smallexample +void foo(void (*fn)(int*), int *data) +@{ + ... + fn(data); + ... +@} +@end smallexample + +This is particuarly useful for cases, where body of functions with callbacks +is unknown at compile-time. Using this attribute allows GCC to perform +optimizations on the callback function, namely constant propagation. +The parameter @var{function-pos} specifies the position of the pointer +to the callback function. All indices start from 1. This parameter should be +followed by @var{n} positions of arguments passed to the callback function +(where @var{n} is the number of arguments the callback function takes) in order +in which they are passed. Value 0 should be used in places where the position +for a given argument is unknown or the value is not passed through the caller. +When used with non-static C++ methods, all indices should start at 2, since the +first argument is implicit @code{this}. + +In the example above, function @code{foo} takes it's callback function as it's +first argument and passes it it's second argument, so the correct values of +parameters are 1 and 2. + @cindex @code{cold} function attribute @item cold The @code{cold} attribute on functions is used to inform the compiler that diff --git a/gcc/fortran/f95-lang.cc b/gcc/fortran/f95-lang.cc index 1f09553142d..009701faa3f 100644 --- a/gcc/fortran/f95-lang.cc +++ b/gcc/fortran/f95-lang.cc @@ -580,6 +580,10 @@ gfc_builtin_function (tree decl) #define ATTR_COLD_NORETURN_NOTHROW_LEAF_LIST \ (ECF_COLD | ECF_NORETURN | \ ECF_NOTHROW | ECF_LEAF) +#define ATTR_CALLBACK_GOMP_LIST (ECF_CB_1_2 | ATTR_NOTHROW_LIST) +#define ATTR_CALLBACK_GOMP_TASK_LIST \ + (ECF_CB_3_0_2 | ECF_CB_1_0 | ATTR_NOTHROW_LIST) +#define ATTR_CALLBACK_OACC_LIST (ECF_CB_2_4 | ATTR_NOTHROW_LIST) static void gfc_define_builtin (const char *name, tree type, enum built_in_function code, diff --git a/gcc/ipa-cp.cc b/gcc/ipa-cp.cc index b4b96997d75..c706a3195b6 100644 --- a/gcc/ipa-cp.cc +++ b/gcc/ipa-cp.cc @@ -131,7 +131,7 @@ along with GCC; see the file COPYING3. If not see #include "dbgcnt.h" #include "symtab-clones.h" #include "gimple-range.h" - +#include "attr-callback.h" /* Allocation pools for values and their sources in ipa-cp. */ @@ -6241,6 +6241,68 @@ identify_dead_nodes (struct cgraph_node *node) } } +/* Removes all useless callback edges from the callgraph. Useless callback edges + might mess up the callgraph, because they might be impossible to redirect and + so on, leading to crashes. Their usefulness is evaluated through + callback_edge_useful_p*/ +static void +purge_useless_callback_edges () +{ + if (dump_file) + fprintf (dump_file, "\nPurging useless callback edges:\n"); + + cgraph_edge *e; + cgraph_node *node; + FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node) + { + for (e = node->callees; e; e = e->next_callee) + { + if (e->has_callback) + { + if (dump_file) + fprintf (dump_file, "\tExamining children of edge %s -> %s:\n", + e->caller->name (), e->callee->name ()); + if (!lookup_attribute ("callback", + DECL_ATTRIBUTES (e->callee->decl))) + { + if (dump_file) + fprintf ( + dump_file, + "\t\tPurging children, because the offloading " + "function no longer has any callback attributes.\n"); + e->purge_callback_children (); + continue; + } + cgraph_edge *cbe, *next; + for (cbe = e->first_callback_target (); cbe; cbe = next) + { + next = cbe->next_callback_target (); + if (!callback_edge_useful_p (cbe)) + { + if (dump_file) + fprintf (dump_file, + "\t\tCallback edge %s -> %s not deemed " + "useful, removing.\n", + cbe->caller->name (), cbe->callee->name ()); + cgraph_edge::remove (cbe); + } + else + { + if (dump_file) + fprintf (dump_file, + "\t\tNot considering callback edge %s -> %s " + "for deletion.\n", + cbe->caller->name (), cbe->callee->name ()); + } + } + } + } + } + + if (dump_file) + fprintf (dump_file, "\n"); +} + /* The decision stage. Iterate over the topological order of call graph nodes TOPO and make specialized clones if deemed beneficial. */ @@ -6271,6 +6333,11 @@ ipcp_decision_stage (class ipa_topo_info *topo) if (change) identify_dead_nodes (node); } + + /* Currently, the primary use of callback edges is constant propagation. + Constant propagation is now over, so we have to remove unused callback + edges. */ + purge_useless_callback_edges (); } /* Look up all VR and bits information that we have discovered and copy it diff --git a/gcc/ipa-fnsummary.cc b/gcc/ipa-fnsummary.cc index 4c062fe8a0e..fb854fa65db 100644 --- a/gcc/ipa-fnsummary.cc +++ b/gcc/ipa-fnsummary.cc @@ -990,7 +990,10 @@ ipa_call_summary_t::duplicate (struct cgraph_edge *src, info->predicate = NULL; edge_set_predicate (dst, srcinfo->predicate); info->param = srcinfo->param.copy (); - if (!dst->indirect_unknown_callee && src->indirect_unknown_callee) + if (!dst->indirect_unknown_callee && src->indirect_unknown_callee + /* Don't subtract the size when dealing with callback pairs, since the + edge has no real size. */ + && !src->has_callback && !dst->callback) { info->call_stmt_size -= (eni_size_weights.indirect_call_cost - eni_size_weights.call_cost); @@ -3106,6 +3109,25 @@ analyze_function_body (struct cgraph_node *node, bool early) es, es3); } } + + /* If dealing with a parent edge, copy its summary over to its + children as well. */ + if (edge->has_callback) + { + cgraph_edge *child; + for (child = edge->first_callback_target (); child; + child = child->next_callback_target ()) + { + ipa_call_summary *es2 = ipa_call_summaries->get (child); + es2 = ipa_call_summaries->get_create (child); + ipa_call_summaries->duplicate (edge, child, es, es2); + /* Unlike speculative edges, callback edges have no real + size or time; the call doesn't exist. Reflect that in + their summaries. */ + es2->call_stmt_size = 0; + es2->call_stmt_time = 0; + } + } } /* TODO: When conditional jump or switch is known to be constant, but diff --git a/gcc/ipa-inline-analysis.cc b/gcc/ipa-inline-analysis.cc index c5472cb0ff0..b24116a0ca9 100644 --- a/gcc/ipa-inline-analysis.cc +++ b/gcc/ipa-inline-analysis.cc @@ -417,6 +417,11 @@ do_estimate_growth_1 (struct cgraph_node *node, void *data) { gcc_checking_assert (e->inline_failed); + /* Don't count callback edges into growth, since they are never inlined + anyway. */ + if (e->callback) + continue; + if (cgraph_inline_failed_type (e->inline_failed) == CIF_FINAL_ERROR || !opt_for_fn (e->caller->decl, optimize)) { diff --git a/gcc/ipa-inline-transform.cc b/gcc/ipa-inline-transform.cc index d2c9a2da6de..11182b673a9 100644 --- a/gcc/ipa-inline-transform.cc +++ b/gcc/ipa-inline-transform.cc @@ -798,7 +798,17 @@ inline_transform (struct cgraph_node *node) if (!e->inline_failed) has_inline = true; next = e->next_callee; - cgraph_edge::redirect_call_stmt_to_callee (e); + if (e->has_callback) + { + /* Redirect child edges when redirecting their parent. */ + cgraph_edge *cbe; + cgraph_edge::redirect_call_stmt_to_callee (e); + for (cbe = e->first_callback_target (); cbe; + cbe = cbe->next_callback_target ()) + cgraph_edge::redirect_call_stmt_to_callee (cbe); + } + else + cgraph_edge::redirect_call_stmt_to_callee (e); } node->remove_all_references (); diff --git a/gcc/ipa-inline.cc b/gcc/ipa-inline.cc index d9fc111a9e7..78dbf3c4f65 100644 --- a/gcc/ipa-inline.cc +++ b/gcc/ipa-inline.cc @@ -373,6 +373,11 @@ can_inline_edge_p (struct cgraph_edge *e, bool report, { gcc_checking_assert (e->inline_failed); + /* Never inline callback edges, since the call doesn't exist in + reality. */ + if (e->callback) + return false; + if (cgraph_inline_failed_type (e->inline_failed) == CIF_FINAL_ERROR) { if (report) diff --git a/gcc/ipa-param-manipulation.cc b/gcc/ipa-param-manipulation.cc index 9b74fe24cc4..7fbe51d729c 100644 --- a/gcc/ipa-param-manipulation.cc +++ b/gcc/ipa-param-manipulation.cc @@ -308,6 +308,16 @@ drop_type_attribute_if_params_changed_p (tree name) return false; } +/* Return TRUE if the attribute should be dropped in the decl it is sitting on + changes. Primarily affects attributes working with the decls arguments. */ +static bool +drop_decl_attribute_if_params_changed_p (tree name) +{ + if (is_attribute_p ("callback", name)) + return true; + return false; +} + /* Build and return a function type just like ORIG_TYPE but with parameter types given in NEW_PARAM_TYPES - which can be NULL if, but only if, ORIG_TYPE itself has NULL TREE_ARG_TYPEs. If METHOD2FUNC is true, also make @@ -488,11 +498,12 @@ ipa_param_adjustments::method2func_p (tree orig_type) performing all atored modifications. TYPE_ORIGINAL_P should be true when OLD_TYPE refers to the type before any IPA transformations, as opposed to a type that can be an intermediate one in between various IPA - transformations. */ + transformations. Set pointee of ARGS_MODIFIED (if provided) to TRUE if the + type's arguments were changed. */ tree -ipa_param_adjustments::build_new_function_type (tree old_type, - bool type_original_p) +ipa_param_adjustments::build_new_function_type ( + tree old_type, bool type_original_p, bool *args_modified /* = NULL */) { auto_vec<tree,16> new_param_types, *new_param_types_p; if (prototype_p (old_type)) @@ -518,6 +529,8 @@ ipa_param_adjustments::build_new_function_type (tree old_type, || get_original_index (index) != (int)index) modified = true; + if (args_modified) + *args_modified = modified; return build_adjusted_function_type (old_type, new_param_types_p, method2func_p (old_type), m_skip_return, @@ -536,10 +549,11 @@ ipa_param_adjustments::adjust_decl (tree orig_decl) { tree new_decl = copy_node (orig_decl); tree orig_type = TREE_TYPE (orig_decl); + bool args_modified = false; if (prototype_p (orig_type) || (m_skip_return && !VOID_TYPE_P (TREE_TYPE (orig_type)))) { - tree new_type = build_new_function_type (orig_type, false); + tree new_type = build_new_function_type (orig_type, false, &args_modified); TREE_TYPE (new_decl) = new_type; } if (method2func_p (orig_type)) @@ -556,6 +570,20 @@ ipa_param_adjustments::adjust_decl (tree orig_decl) if (m_skip_return) DECL_IS_MALLOC (new_decl) = 0; + /* If the decl's arguments changed, we might need to drop some attributes. */ + if (args_modified && DECL_ATTRIBUTES (new_decl)) + { + tree t = DECL_ATTRIBUTES (new_decl); + tree *last = &DECL_ATTRIBUTES (new_decl); + DECL_ATTRIBUTES (new_decl) = NULL; + for (; t; t = TREE_CHAIN (t)) + if (!drop_decl_attribute_if_params_changed_p (get_attribute_name (t))) + { + *last = copy_node (t); + TREE_CHAIN (*last) = NULL; + last = &TREE_CHAIN (*last); + } + } return new_decl; } diff --git a/gcc/ipa-param-manipulation.h b/gcc/ipa-param-manipulation.h index 7c7661c1b4a..ecd564da9a0 100644 --- a/gcc/ipa-param-manipulation.h +++ b/gcc/ipa-param-manipulation.h @@ -229,7 +229,7 @@ public: /* Return if the first parameter is left intact. */ bool first_param_intact_p (); /* Build a function type corresponding to the modified call. */ - tree build_new_function_type (tree old_type, bool type_is_original_p); + tree build_new_function_type (tree old_type, bool type_is_original_p, bool *args_modified = NULL); /* Build a declaration corresponding to the target of the modified call. */ tree adjust_decl (tree orig_decl); /* Fill a vector marking which parameters are intact by the described diff --git a/gcc/ipa-prop.cc b/gcc/ipa-prop.cc index 0398d69962f..97f48c46b16 100644 --- a/gcc/ipa-prop.cc +++ b/gcc/ipa-prop.cc @@ -61,6 +61,8 @@ along with GCC; see the file COPYING3. If not see #include "value-range-storage.h" #include "vr-values.h" #include "lto-streamer.h" +#include "attribs.h" +#include "attr-callback.h" /* Function summary where the parameter infos are actually stored. */ ipa_node_params_t *ipa_node_params_sum = NULL; @@ -324,6 +326,10 @@ ipa_get_param_decl_index (class ipa_node_params *info, tree ptree) return ipa_get_param_decl_index_1 (info->descriptors, ptree); } +static void +ipa_duplicate_jump_function (cgraph_edge *src, cgraph_edge *dst, + ipa_jump_func *src_jf, ipa_jump_func *dst_jf); + /* Populate the param_decl field in parameter DESCRIPTORS that correspond to NODE. */ @@ -2415,6 +2421,18 @@ skip_a_safe_conversion_op (tree t) return t; } +/* Initializes ipa_edge_args summary of CBE given it's parent edge. + This primarily means allocating the correct amount of jump functions. */ + +static inline void +init_callback_edge_summary (struct cgraph_edge *parent, struct cgraph_edge *cbe) +{ + ipa_edge_args *parent_args = ipa_edge_args_sum->get (parent); + ipa_edge_args *cb_args = ipa_edge_args_sum->get_create (cbe); + vec_safe_grow_cleared (cb_args->jump_functions, + parent_args->jump_functions->length (), true); +} + /* Compute jump function for all arguments of callsite CS and insert the information in the jump_functions array in the ipa_edge_args corresponding to this callsite. */ @@ -2440,6 +2458,7 @@ ipa_compute_jump_functions_for_edge (struct ipa_func_body_info *fbi, if (ipa_func_spec_opts_forbid_analysis_p (cs->caller)) return; + auto_vec<cgraph_edge*> callback_edges; for (n = 0; n < arg_num; n++) { struct ipa_jump_func *jfunc = ipa_get_ith_jump_func (args, n); @@ -2518,10 +2537,43 @@ ipa_compute_jump_functions_for_edge (struct ipa_func_body_info *fbi, arg = skip_a_safe_conversion_op (arg); if (is_gimple_ip_invariant (arg) - || (VAR_P (arg) - && is_global_var (arg) - && TREE_READONLY (arg))) - ipa_set_jf_constant (jfunc, arg, cs); + || (VAR_P (arg) && is_global_var (arg) && TREE_READONLY (arg))) + { + ipa_set_jf_constant (jfunc, arg, cs); + if (TREE_CODE (arg) == ADDR_EXPR) + { + tree pointee = TREE_OPERAND (arg, 0); + if (TREE_CODE (pointee) == FUNCTION_DECL && !cs->callback + && cs->callee) + { + /* Argument is a pointer to a function. Look for a callback + attribute describing this argument. */ + tree callback_attr + = lookup_attribute ("callback", + DECL_ATTRIBUTES (cs->callee->decl)); + for (; callback_attr; + callback_attr + = lookup_attribute ("callback", + TREE_CHAIN (callback_attr))) + if (callback_get_fn_index (callback_attr) == n) + break; + /* If a callback attribute describing this pointer is found, + create a callback edge to pointee function to allow for + further optimizations. */ + if (callback_attr) + { + cgraph_node *kernel_node + = cgraph_node::get_create (pointee); + unsigned callback_hash + = callback_hash_attr (callback_attr); + cgraph_edge *cbe + = cs->make_callback (kernel_node, callback_hash); + init_callback_edge_summary (cs, cbe); + callback_edges.safe_push (cbe); + } + } + } + } else if (!is_gimple_reg_type (TREE_TYPE (arg)) && TREE_CODE (arg) == PARM_DECL) { @@ -2579,6 +2631,32 @@ ipa_compute_jump_functions_for_edge (struct ipa_func_body_info *fbi, || POINTER_TYPE_P (param_type))) determine_known_aggregate_parts (fbi, call, arg, param_type, jfunc); } + + if (!callback_edges.is_empty ()) + { + /* For every callback edge, fetch jump functions of arguments + passed to them and copy them over to their respective summaries. + This avoids recalculating them for every callback edge, since their + arguments are just passed through. */ + unsigned j; + for (j = 0; j < callback_edges.length (); j++) + { + cgraph_edge *callback_edge = callback_edges[j]; + ipa_edge_args *cb_summary + = ipa_edge_args_sum->get_create (callback_edge); + auto_vec<int> arg_mapping + = callback_get_arg_mapping (callback_edge, cs); + unsigned i; + for (i = 0; i < arg_mapping.length (); i++) + { + class ipa_jump_func *src + = ipa_get_ith_jump_func (args, arg_mapping[i]); + class ipa_jump_func *dst = ipa_get_ith_jump_func (cb_summary, i); + ipa_duplicate_jump_function (cs, callback_edge, src, dst); + } + } + } + if (!useful_context) vec_free (args->polymorphic_call_contexts); } diff --git a/gcc/lto-cgraph.cc b/gcc/lto-cgraph.cc index 8439c51fb2b..ab522735850 100644 --- a/gcc/lto-cgraph.cc +++ b/gcc/lto-cgraph.cc @@ -274,6 +274,9 @@ lto_output_edge (struct lto_simple_output_block *ob, struct cgraph_edge *edge, bp_pack_value (&bp, edge->speculative_id, 16); bp_pack_value (&bp, edge->indirect_inlining_edge, 1); bp_pack_value (&bp, edge->speculative, 1); + bp_pack_value (&bp, edge->callback, 1); + bp_pack_value (&bp, edge->has_callback, 1); + bp_pack_value (&bp, edge->callback_hash, 16); bp_pack_value (&bp, edge->call_stmt_cannot_inline_p, 1); gcc_assert (!edge->call_stmt_cannot_inline_p || edge->inline_failed != CIF_BODY_NOT_AVAILABLE); @@ -1538,6 +1541,9 @@ input_edge (class lto_input_block *ib, vec<symtab_node *> nodes, edge->indirect_inlining_edge = bp_unpack_value (&bp, 1); edge->speculative = bp_unpack_value (&bp, 1); + edge->callback = bp_unpack_value(&bp, 1); + edge->has_callback = bp_unpack_value(&bp, 1); + edge->callback_hash = bp_unpack_value(&bp, 16); edge->lto_stmt_uid = stmt_id; edge->speculative_id = speculative_id; edge->inline_failed = inline_failed; diff --git a/gcc/omp-builtins.def b/gcc/omp-builtins.def index f73fb7b9dd8..ec7750e2f4b 100644 --- a/gcc/omp-builtins.def +++ b/gcc/omp-builtins.def @@ -42,7 +42,7 @@ DEF_GOACC_BUILTIN (BUILT_IN_GOACC_EXIT_DATA, "GOACC_exit_data", ATTR_NOTHROW_LIST) DEF_GOACC_BUILTIN (BUILT_IN_GOACC_PARALLEL, "GOACC_parallel_keyed", BT_FN_VOID_INT_OMPFN_SIZE_PTR_PTR_PTR_VAR, - ATTR_NOTHROW_LIST) + ATTR_CALLBACK_OACC_LIST) DEF_GOACC_BUILTIN (BUILT_IN_GOACC_UPDATE, "GOACC_update", BT_FN_VOID_INT_SIZE_PTR_PTR_PTR_INT_INT_VAR, ATTR_NOTHROW_LIST) @@ -355,35 +355,35 @@ DEF_GOMP_BUILTIN (BUILT_IN_GOMP_LOOP_ULL_ORDERED_RUNTIME_NEXT, DEF_GOMP_BUILTIN (BUILT_IN_GOMP_PARALLEL_LOOP_STATIC, "GOMP_parallel_loop_static", BT_FN_VOID_OMPFN_PTR_UINT_LONG_LONG_LONG_LONG_UINT, - ATTR_NOTHROW_LIST) + ATTR_CALLBACK_GOMP_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_PARALLEL_LOOP_DYNAMIC, "GOMP_parallel_loop_dynamic", BT_FN_VOID_OMPFN_PTR_UINT_LONG_LONG_LONG_LONG_UINT, - ATTR_NOTHROW_LIST) + ATTR_CALLBACK_GOMP_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_PARALLEL_LOOP_GUIDED, "GOMP_parallel_loop_guided", BT_FN_VOID_OMPFN_PTR_UINT_LONG_LONG_LONG_LONG_UINT, - ATTR_NOTHROW_LIST) + ATTR_CALLBACK_GOMP_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_PARALLEL_LOOP_RUNTIME, "GOMP_parallel_loop_runtime", BT_FN_VOID_OMPFN_PTR_UINT_LONG_LONG_LONG_UINT, - ATTR_NOTHROW_LIST) + ATTR_CALLBACK_GOMP_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_PARALLEL_LOOP_NONMONOTONIC_DYNAMIC, "GOMP_parallel_loop_nonmonotonic_dynamic", BT_FN_VOID_OMPFN_PTR_UINT_LONG_LONG_LONG_LONG_UINT, - ATTR_NOTHROW_LIST) + ATTR_CALLBACK_GOMP_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_PARALLEL_LOOP_NONMONOTONIC_GUIDED, "GOMP_parallel_loop_nonmonotonic_guided", BT_FN_VOID_OMPFN_PTR_UINT_LONG_LONG_LONG_LONG_UINT, - ATTR_NOTHROW_LIST) + ATTR_CALLBACK_GOMP_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_PARALLEL_LOOP_NONMONOTONIC_RUNTIME, "GOMP_parallel_loop_nonmonotonic_runtime", BT_FN_VOID_OMPFN_PTR_UINT_LONG_LONG_LONG_UINT, - ATTR_NOTHROW_LIST) + ATTR_CALLBACK_GOMP_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_PARALLEL_LOOP_MAYBE_NONMONOTONIC_RUNTIME, "GOMP_parallel_loop_maybe_nonmonotonic_runtime", BT_FN_VOID_OMPFN_PTR_UINT_LONG_LONG_LONG_UINT, - ATTR_NOTHROW_LIST) + ATTR_CALLBACK_GOMP_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_LOOP_END, "GOMP_loop_end", BT_FN_VOID, ATTR_NOTHROW_LEAF_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_LOOP_END_CANCEL, "GOMP_loop_end_cancel", @@ -406,13 +406,13 @@ DEF_GOMP_BUILTIN (BUILT_IN_GOMP_INTEROP, "GOMP_interop", BT_FN_VOID_INT_INT_PTR_PTR_PTR_INT_PTR_INT_PTR_UINT_PTR, ATTR_NOTHROW_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_PARALLEL, "GOMP_parallel", - BT_FN_VOID_OMPFN_PTR_UINT_UINT, ATTR_NOTHROW_LIST) + BT_FN_VOID_OMPFN_PTR_UINT_UINT, ATTR_CALLBACK_GOMP_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_PARALLEL_REDUCTIONS, "GOMP_parallel_reductions", - BT_FN_UINT_OMPFN_PTR_UINT_UINT, ATTR_NOTHROW_LIST) + BT_FN_UINT_OMPFN_PTR_UINT_UINT, ATTR_CALLBACK_GOMP_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_TASK, "GOMP_task", BT_FN_VOID_OMPFN_PTR_OMPCPYFN_LONG_LONG_BOOL_UINT_PTR_INT_PTR, - ATTR_NOTHROW_LIST) + ATTR_CALLBACK_GOMP_TASK_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_TASKLOOP, "GOMP_taskloop", BT_FN_VOID_OMPFN_PTR_OMPCPYFN_LONG_LONG_UINT_LONG_INT_LONG_LONG_LONG, ATTR_NOTHROW_LIST) @@ -427,7 +427,7 @@ DEF_GOMP_BUILTIN (BUILT_IN_GOMP_SECTIONS_NEXT, "GOMP_sections_next", BT_FN_UINT, ATTR_NOTHROW_LEAF_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_PARALLEL_SECTIONS, "GOMP_parallel_sections", - BT_FN_VOID_OMPFN_PTR_UINT_UINT_UINT, ATTR_NOTHROW_LIST) + BT_FN_VOID_OMPFN_PTR_UINT_UINT_UINT, ATTR_CALLBACK_GOMP_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_SECTIONS_END, "GOMP_sections_end", BT_FN_VOID, ATTR_NOTHROW_LEAF_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_SECTIONS_END_CANCEL, @@ -468,7 +468,7 @@ DEF_GOMP_BUILTIN (BUILT_IN_GOMP_TARGET_MAP_INDIRECT_PTR, DEF_GOMP_BUILTIN (BUILT_IN_GOMP_TEAMS4, "GOMP_teams4", BT_FN_BOOL_UINT_UINT_UINT_BOOL, ATTR_NOTHROW_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_TEAMS_REG, "GOMP_teams_reg", - BT_FN_VOID_OMPFN_PTR_UINT_UINT_UINT, ATTR_NOTHROW_LIST) + BT_FN_VOID_OMPFN_PTR_UINT_UINT_UINT, ATTR_CALLBACK_GOMP_LIST) DEF_GOMP_BUILTIN (BUILT_IN_GOMP_TASKGROUP_REDUCTION_REGISTER, "GOMP_taskgroup_reduction_register", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST) diff --git a/gcc/testsuite/gcc.dg/attr-callback.c b/gcc/testsuite/gcc.dg/attr-callback.c new file mode 100644 index 00000000000..def371193f5 --- /dev/null +++ b/gcc/testsuite/gcc.dg/attr-callback.c @@ -0,0 +1,79 @@ +/* Test callback attribute error checking. */ + +/* { dg-do compile } */ + +void +__attribute__((callback(1, 2))) +correct_1(void (*)(int*), int*); + +void +__attribute__((callback(1, 2, 3))) +correct_2(void (*)(int*, double*), int*, double*); + +void +__attribute__((callback(1, 0))) +unknown_1(void (*)(int*)); + +void +__attribute__((callback(1, 2, 0))) +unknown_2(void (*)(int*, double*), int*, double*, char*); + +void +__attribute__((callback(1, 0, 3, 3))) +too_many(void (*)(int*, double*), int*, double*); /* { dg-error "argument number mismatch, 2 expected, got 3" }*/ + +void +__attribute__((callback(1, 2))) +too_few_1(void (*)(int*, double*), int*, double*); /* { dg-error "argument number mismatch, 2 expected, got 1" }*/ + +void +__attribute__((callback(1))) +too_few_2(void (*)(int*, double*), int*, double*); /* { dg-error "argument number mismatch, 2 expected, got 0" }*/ + +void +__attribute__((callback(3, 1))) +promotion(char*, float, int (*)(int*)); + +void +__attribute__((callback(2, 3))) +downcast(char*, void* (*)(float*), double*); + +void +__attribute__((callback(1, 2, 5))) +out_of_range_1(char (*)(float*, double*), float*, double*, int*); /* { dg-error "callback argument index 5 is out of range" } */ + +void +__attribute__((callback(1, -2, 3))) +out_of_range_2(char (*)(float*, double*), float*, double*, int*); /* { dg-error "callback argument index -2 is out of range" } */ + +void +__attribute__((callback(-1, 2, 3))) +out_of_range_3(char (*)(float*, double*), float*, double*, int*); /* { dg-error "callback function position out of range" } */ + +void +__attribute__((callback(0, 2, 3))) +unknown_fn(char (*)(float*, double*), float*, double*, int*); /* { dg-error "callback function position cannot be marked as unknown" } */ + +void +__attribute__((callback(1, 2))) +not_a_fn(int, int); /* { dg-error "argument no. 1 is not an address of a function" } */ + +struct S{ + int x; +}; + +void +__attribute__((callback(1, 2))) +incompatible_types_1(void (*)(struct S*), struct S); /* { dg-error "argument type at index 2 is not compatible with callback argument type at index 1" } */ + +void +__attribute__((callback(1, 3, 2))) +incompatible_types_2(void (*)(struct S*, int*), int*, double); /* { dg-error "argument type at index 3 is not compatible with callback argument type at index 1" } */ + +void +__attribute__((callback(1, "2"))) +wrong_arg_type_1(void (*)(void*), void*); /* { dg-error "argument no. 1 is not an integer constant" } */ + +void +__attribute__((callback("not a number", 2, 2))) +wrong_arg_type_2(void (*)(void*, void*), void*); /* { dg-error "argument specifying callback function position is not an integer constant" } */ diff --git a/gcc/testsuite/gcc.dg/ipa/ipcp-cb1.c b/gcc/testsuite/gcc.dg/ipa/ipcp-cb1.c new file mode 100644 index 00000000000..5f672a506f4 --- /dev/null +++ b/gcc/testsuite/gcc.dg/ipa/ipcp-cb1.c @@ -0,0 +1,25 @@ +/* Test that we can propagate constants into outlined OpenMP kernels. + This tests the underlying callback attribute and its related edges. */ + +/* { dg-do run } */ +/* { dg-options "-O3 -fopenmp -flto -std=gnu99 -fdump-ipa-cp-details" } */ +/* { dg-require-effective-target fopenmp } */ +/* { dg-require-effective-target lto } */ + +int a[100]; +void test(int c) { +#pragma omp parallel for + for (int i = 0; i < c; i++) { + if (!__builtin_constant_p(c)) { + __builtin_abort(); + } + a[i] = i; + } +} +int main() { + test(100); + return a[5] - 5; +} + +/* { dg-final { scan-wpa-ipa-dump "Creating a specialized node of test._omp_fn" "cp" } } */ +/* { dg-final { scan-wpa-ipa-dump "Aggregate replacements: 0\\\[0]=100\\(by_ref\\)" "cp" } } */ diff --git a/gcc/testsuite/gcc.dg/ipa/ipcp-cb2.c b/gcc/testsuite/gcc.dg/ipa/ipcp-cb2.c new file mode 100644 index 00000000000..b42c2a09d8b --- /dev/null +++ b/gcc/testsuite/gcc.dg/ipa/ipcp-cb2.c @@ -0,0 +1,53 @@ +/* Test that we can handle multiple callback attributes and use them to + propagate into callbacks. 'cb1' body borrowed from a ipa-cp test to get the + pass to work. */ + +/* { dg-xfail-if "Linking will fail" { *-*-* } } */ +/* { dg-do link } */ +/* { dg-options "-O3 -flto -fdump-ipa-cp-details" } */ +/* { dg-require-effective-target lto } */ + +struct S { + int a, b, c; +}; + +extern void *blah(int, void *); + +extern __attribute__((callback(1, 2), callback(3, 4, 5))) void +call(void (*fn1)(struct S *), struct S *a, void (*fn2)(struct S *, struct S *), + struct S *b, struct S *c); + +void cb1(struct S *p) { + int i, c = p->c; + int b = p->b; + void *v = (void *)p; + + for (i = 0; i < c; i++) + v = blah(b + i, v); +} + +void cb2(struct S *a, struct S *b) { + cb1(a); + cb1(b); +} + +void test(int a, int b, int c) { + struct S s; + s.a = a; + s.b = b; + s.c = c; + struct S ss; + ss.a = s.c; + ss.b = s.b; + ss.c = s.a; + call(cb1, &s, cb2, &s, &ss); +} + +int main() { + test(1, 64, 32); + return 0; +} + +/* { dg-final { scan-wpa-ipa-dump "Creating a specialized node of cb1" "cp" } } */ +/* { dg-final { scan-wpa-ipa-dump "Creating a specialized node of cb2" "cp" } } */ +/* { dg-final { scan-wpa-ipa-dump-times "Aggregate replacements: " 2 "cp" } } */ diff --git a/gcc/tree-core.h b/gcc/tree-core.h index bd19c99d326..37fd0322211 100644 --- a/gcc/tree-core.h +++ b/gcc/tree-core.h @@ -98,6 +98,20 @@ struct die_struct; /* Nonzero if this is a function expected to end with an exception. */ #define ECF_XTHROW (1 << 16) +/* Flags for various callback attribute combinations. */ + +/* callback(1, 0) */ +#define ECF_CB_1_0 (1 << 17) + +/* callback(1, 2) */ +#define ECF_CB_1_2 (1 << 18) + +/* callback(2, 4) */ +#define ECF_CB_2_4 (1 << 19) + +/* callback(3, 0, 2) */ +#define ECF_CB_3_0_2 (1 << 20) + /* Call argument flags. */ /* Nonzero if the argument is not used by the function. */ diff --git a/gcc/tree-inline.cc b/gcc/tree-inline.cc index 3289b4f6d05..a8cbc6ed8e0 100644 --- a/gcc/tree-inline.cc +++ b/gcc/tree-inline.cc @@ -2356,6 +2356,19 @@ copy_bb (copy_body_data *id, basic_block bb, indirect->count = copy_basic_block->count.apply_probability (prob); } + /* If edge is a callback parent edge, copy all its + children as well */ + else if (edge->has_callback) + { + edge + = edge->clone (id->dst_node, call_stmt, + gimple_uid (stmt), num, den, true); + cgraph_edge *e; + for (e = old_edge->first_callback_target (); e; + e = e->next_callback_target ()) + edge = e->clone (id->dst_node, call_stmt, + gimple_uid (stmt), num, den, true); + } else { edge = edge->clone (id->dst_node, call_stmt, @@ -3051,8 +3064,18 @@ redirect_all_calls (copy_body_data * id, basic_block bb) { if (!id->killed_new_ssa_names) id->killed_new_ssa_names = new hash_set<tree> (16); - cgraph_edge::redirect_call_stmt_to_callee (edge, - id->killed_new_ssa_names); + cgraph_edge::redirect_call_stmt_to_callee ( + edge, id->killed_new_ssa_names); + if (edge->has_callback) + { + /* When redirecting a parent edge, we need to redirect its + children as well. */ + cgraph_edge *cbe; + for (cbe = edge->first_callback_target (); cbe; + cbe = cbe->next_callback_target ()) + cgraph_edge::redirect_call_stmt_to_callee ( + cbe, id->killed_new_ssa_names); + } if (stmt == last && id->call_stmt && maybe_clean_eh_stmt (stmt)) gimple_purge_dead_eh_edges (bb); diff --git a/gcc/tree.cc b/gcc/tree.cc index eccfcc89da4..e936f4d874e 100644 --- a/gcc/tree.cc +++ b/gcc/tree.cc @@ -9926,6 +9926,48 @@ set_call_expr_flags (tree decl, int flags) DECL_ATTRIBUTES (decl) = tree_cons (get_identifier ("expected_throw"), NULL, DECL_ATTRIBUTES (decl)); + + if (flags & ECF_CB_1_0) + { + tree args + = tree_cons (NULL_TREE, build_int_cst (integer_type_node, 1), + build_tree_list (NULL_TREE, + build_int_cst (integer_type_node, 0))); + DECL_ATTRIBUTES (decl) + = tree_cons (get_identifier ("callback"), args, DECL_ATTRIBUTES (decl)); + } + + if (flags & ECF_CB_1_2) + { + tree args + = tree_cons (NULL_TREE, build_int_cst (integer_type_node, 1), + build_tree_list (NULL_TREE, + build_int_cst (integer_type_node, 2))); + DECL_ATTRIBUTES (decl) + = tree_cons (get_identifier ("callback"), args, DECL_ATTRIBUTES (decl)); + } + + if (flags & ECF_CB_2_4) + { + tree args + = tree_cons (NULL_TREE, build_int_cst (integer_type_node, 2), + build_tree_list (NULL_TREE, + build_int_cst (integer_type_node, 4))); + DECL_ATTRIBUTES (decl) + = tree_cons (get_identifier ("callback"), args, DECL_ATTRIBUTES (decl)); + } + + if (flags & ECF_CB_3_0_2) + { + tree args = tree_cons ( + NULL_TREE, build_int_cst (integer_type_node, 3), + tree_cons (NULL_TREE, build_int_cst (integer_type_node, 0), + build_tree_list (NULL_TREE, + build_int_cst (integer_type_node, 2)))); + DECL_ATTRIBUTES (decl) + = tree_cons (get_identifier ("callback"), args, DECL_ATTRIBUTES (decl)); + } + /* Looping const or pure is implied by noreturn. There is currently no way to declare looping const or looping pure alone. */ gcc_assert (!(flags & ECF_LOOPING_CONST_OR_PURE) -- 2.49.0