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