On Sun, Apr 27, 2025 at 2:58 AM Josef Melcr <melcr...@fit.cvut.cz> wrote: > > 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?
This seems like it could also improve code dealing with C++ lambdas. Have you thought of that? Thanks, Andrew > > 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 >