> 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 is a second version of this patch.  Changes made in this version:
- Attribute is now called " callback" and is thus outside of the public
  API.  I removed its docs and tests which no longer apply.
- GOMP_task no longer has the attribute and uses it on demand.  The
  attribute is freshly created when needed, which is about 3 times per
  child edge.  I think the extra allocations are worth it when
  considering code readability.
- Edge redirection no longer leaves dangling refs.
- Formatting issues shoud be resolved.

Boostrapped and regtested on x86_64-pc-linux-gnu.

gcc/ChangeLog:

        * builtin-attrs.def (ATTR_CALLBACK): Callback attr identifier.
        (DEF_CALLBACK_ATTRIBUTE): Macro for callback attr creation.
        (GOMP): Attrs for libgomp functions.
        (OACC): Attrs for oacc functions.
        (ATTR_CALLBACK_GOMP_LIST): ATTR_NOTHROW_LIST with GOMP callback
        attr added.
        (ATTR_CALLBACK_OACC_LIST): ATTR_NOTHROW_LIST with OACC callback
        attr added.
        * cgraph.cc (cgraph_add_edge_to_call_site_hash): Always hash the
        parent edge.
        (cgraph_node::get_edge): Always return the parent edge.
        (cgraph_edge::set_call_stmt): Add cascade for callback child
        edges.
        (symbol_table::create_edge): Allow callback edges to share the
        same call statement, initialize new flags.
        (cgraph_edge::make_callback): New method, derives a new callback
        edge.
        (cgraph_edge::get_callback_parent_edge): New method.
        (cgraph_edge::first_callback_target): Likewise.
        (cgraph_edge::next_callback_target): Likewise.
        (cgraph_edge::purge_callback_children): Likewise.
        (cgraph_edge::redirect_callee): When redirecting a callback
        edge, redirects its ref as well.
        (cgraph_edge::redirect_call_stmt_to_callee): Add callback edge
        redirection, set child call stmt when setting their parent.
        (cgraph_node::remove_callers): Add cascade for child edges.
        (cgraph_edge::dump_edge_flags): Add printing for callback flags.
        (cgraph_node::verify_node): Add sanity checks for callback
        edges.
        * cgraph.h: Add new flags and 16 bit callback hash to
        cgraph_edge class.
        * cgraphclones.cc (cgraph_edge::clone): Copy over callback data.
        * ipa-cp.cc (purge_useless_callback_edges): New function, purges
        callback edges when needed.
        (ipcp_decision_stage): Call purge_useless_callback_edges.
        * 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
        child edges when estimating growth.
        * ipa-inline-transform.cc (inline_transform): Add redirection
        cascade for child edges.
        * ipa-inline.cc (can_inline_edge_p): Never inline child edges.
        * ipa-param-manipulation.cc (drop_decl_attribute_if_params_changed_p): 
New function.
        (ipa_param_adjustments::build_new_function_type): Add
        args_modified out parameter.
        (ipa_param_adjustments::adjust_decl): Drop callback attrs when
        args are modified.
        * ipa-param-manipulation.h: Adjust decl 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): Add callback edge
        creation logic.
        * lto-cgraph.cc (lto_output_edge): Stream out callback data.
        (input_edge): Input callback data.
        * omp-builtins.def (BUILT_IN_GOACC_PARALLEL): Use its
        corresponding callback attr list.
        (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_PARALLEL_SECTIONS): Likewise.
        (BUILT_IN_GOMP_TEAMS_REG): Likewise.
        * tree-core.h (ECF_CB_1_2): New constant for callback(1,2).
        (ECF_CB_2_4): New constant for callback(2,4).
        * tree-inline.cc (copy_bb): Copy child edges when copying
        parent.
        (redirect_all_calls): Redirect child edges.
        * tree.cc (set_call_expr_flags): Create callback attrs according
        to the ECF_CB constants.
        * attr-callback.h: New file.

gcc/c-family/ChangeLog:

        * c-attribs.cc: Define callback attribute.

gcc/fortran/ChangeLog:

        * f95-lang.cc (ATTR_CALLBACK_GOMP_LIST): New attr list
        corresponding to the list in builtin-attrs.def.
        (ATTR_CALLBACK_OACC_LIST): Likewise.

gcc/testsuite/ChangeLog:

        * gcc.dg/ipa/ipcp-cb-spec1.c: New test.
        * gcc.dg/ipa/ipcp-cb-spec2.c: New test.
        * gcc.dg/ipa/ipcp-cb1.c: New test.

Signed-off-by: Josef Melcr <jmelc...@gmail.com>
---
 gcc/attr-callback.h                      | 382 +++++++++++++++++++++++
 gcc/builtin-attrs.def                    |  14 +
 gcc/c-family/c-attribs.cc                |   3 +
 gcc/cgraph.cc                            | 277 +++++++++++++++-
 gcc/cgraph.h                             |  42 +++
 gcc/cgraphclones.cc                      |   3 +
 gcc/fortran/f95-lang.cc                  |   2 +
 gcc/ipa-cp.cc                            |  70 ++++-
 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            |  37 ++-
 gcc/ipa-param-manipulation.h             |   2 +-
 gcc/ipa-prop.cc                          | 101 +++++-
 gcc/lto-cgraph.cc                        |   6 +
 gcc/omp-builtins.def                     |  26 +-
 gcc/testsuite/gcc.dg/ipa/ipcp-cb-spec1.c |  19 ++
 gcc/testsuite/gcc.dg/ipa/ipcp-cb-spec2.c |  21 ++
 gcc/testsuite/gcc.dg/ipa/ipcp-cb1.c      |  25 ++
 gcc/tree-core.h                          |   8 +
 gcc/tree-inline.cc                       |  27 +-
 gcc/tree.cc                              |  18 +-
 23 files changed, 1094 insertions(+), 35 deletions(-)
 create mode 100644 gcc/attr-callback.h
 create mode 100644 gcc/testsuite/gcc.dg/ipa/ipcp-cb-spec1.c
 create mode 100644 gcc/testsuite/gcc.dg/ipa/ipcp-cb-spec2.c
 create mode 100644 gcc/testsuite/gcc.dg/ipa/ipcp-cb1.c

diff --git a/gcc/attr-callback.h b/gcc/attr-callback.h
new file mode 100644
index 00000000000..3558e1a64f5
--- /dev/null
+++ b/gcc/attr-callback.h
@@ -0,0 +1,382 @@
+/* Callback attribute handling
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   Contributed by Josef Melcr <jmelc...@gmail.com>
+
+   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 "diagnostic.h"
+#include "cgraph.h"
+#include "stringpool.h"
+#include "system.h"
+#include "tree-core.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
+};
+
+#define CALLBACK_ATTR_IDENT " callback"
+
+/* Returns a callback attribute with callback index FN_IDX, and ARG_COUNT
+   arguments specified by VA_ARGS.  */
+inline tree
+callback_build_attr (unsigned fn_idx, unsigned arg_count...)
+{
+  va_list args;
+  va_start (args, arg_count);
+
+  tree cblist = NULL_TREE;
+  tree *pp = &cblist;
+  unsigned i;
+  for (i = 0; i < arg_count; i++)
+    {
+      int num = va_arg (args, int);
+      tree tnum = build_int_cst (integer_type_node, num);
+      *pp = build_tree_list (NULL, tnum PASS_MEM_STAT);
+      pp = &TREE_CHAIN (*pp);
+    }
+  cblist
+    = tree_cons (NULL_TREE, build_int_cst (integer_type_node, fn_idx), cblist);
+  tree attr
+    = tree_cons (get_identifier (CALLBACK_ATTR_IDENT), cblist, NULL_TREE);
+  return attr;
+}
+
+/* Returns TRUE if a function should be treated as if it had a callback
+   attribute despite the DECL not having it.  */
+inline bool
+callback_is_special_cased (tree decl, gcall *stmt)
+{
+  if (fndecl_built_in_p (decl, BUILT_IN_GOMP_TASK))
+    {
+      if (stmt)
+       {
+         return gimple_call_arg (stmt, 2) == null_pointer_node;
+       }
+      return true;
+    }
+  return false;
+}
+
+/* Returns an attribute for a special cased function.  */
+inline tree
+callback_special_case_attr (tree decl)
+{
+  if (fndecl_built_in_p (decl, BUILT_IN_GOMP_TASK))
+    return callback_build_attr (1, 1, 2);
+  gcc_unreachable ();
+}
+
+/* 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);
+
+  if (callback_is_special_cased (parent->callee->decl, e->call_stmt))
+    return callback_special_case_attr (parent->callee->decl);
+
+  tree cb_attr = lookup_attribute (CALLBACK_ATTR_IDENT,
+                                  DECL_ATTRIBUTES (parent->callee->decl));
+  gcc_checking_assert (cb_attr);
+  tree res = NULL_TREE;
+  for (; cb_attr;
+       cb_attr = lookup_attribute (CALLBACK_ATTR_IDENT, 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..722c2892e33 100644
--- a/gcc/builtin-attrs.def
+++ b/gcc/builtin-attrs.def
@@ -122,6 +122,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 +417,19 @@ DEF_FORMAT_ATTRIBUTE_NOTHROW(STRFMON,3,3_4)
 #undef DEF_FORMAT_ATTRIBUTE_NOTHROW
 #undef DEF_FORMAT_ATTRIBUTE_BOTH
 
+/* Construct callback attributes for GOMP builtins.  */
+#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, 2)
+DEF_CALLBACK_ATTRIBUTE(OACC, 2, 4)
+DEF_ATTR_TREE_LIST(ATTR_CALLBACK_GOMP_LIST, ATTR_CALLBACK,
+                   ATTR_CALLBACK_GOMP_1_2, ATTR_NOTHROW_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..033a9c072ac 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_ATTR_IDENT,      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 94a2e6e6105..42859bfd51d 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"
@@ -870,11 +871,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))
@@ -918,6 +929,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);
@@ -987,8 +1005,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
@@ -1035,7 +1076,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));
     }
@@ -1062,6 +1103,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);
@@ -1285,6 +1329,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.
 
@@ -1522,12 +1677,27 @@ void
 cgraph_edge::redirect_callee (cgraph_node *n)
 {
   bool loc = callee->comdat_local_p ();
+  cgraph_node *old_callee = callee;
+
   /* Remove from callers list of the current callee.  */
   remove_callee ();
 
   /* Insert to callers list of the new callee.  */
   set_callee (n);
 
+  if (callback)
+    {
+      /* When redirecting a callback callee, redirect its ref as well.  */
+      ipa_ref *old_ref = caller->find_reference (old_callee, call_stmt,
+                                                lto_stmt_uid, IPA_REF_ADDR);
+      gcc_checking_assert(old_ref);
+      old_ref->remove_reference ();
+      ipa_ref *new_ref = caller->create_reference (n, IPA_REF_ADDR, call_stmt);
+      new_ref->lto_stmt_uid = lto_stmt_uid;
+      if (!old_callee->referred_to_p ())
+       old_callee->address_taken = 0;
+    }
+
   if (!inline_failed)
     return;
   if (!loc && n->comdat_local_p ())
@@ -1644,6 +1814,25 @@ 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 (!callback_is_special_cased (parent->callee->decl, e->call_stmt)
+         && !lookup_attribute (CALLBACK_ATTR_IDENT,
+                               DECL_ATTRIBUTES (parent->callee->decl)))
+       /* Callback attribute is removed if the offloading function changes
+          signature, as the indices wouldn't 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);
+      tree new_addr = build_addr (e->callee->decl);
+      gimple_call_set_arg (e->call_stmt, fn_idx, new_addr);
+      return e->call_stmt;
+    }
+
   if (decl && ipa_saved_clone_sources)
     {
       tree *p = ipa_saved_clone_sources->get (e->callee);
@@ -1753,7 +1942,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 pairing would fall apart.  */
+  e->caller->set_call_stmt_including_clones (e->call_stmt, new_stmt, 
e->has_callback);
 
   if (symtab->dump_file)
     {
@@ -1932,6 +2123,16 @@ 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 ();
+             cgraph_edge::remove (cbe);
+           }
+       }
       symtab->call_edge_removal_hooks (e);
       e->remove_caller ();
       symtab->free_edge (e);
@@ -2241,6 +2442,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)
@@ -3846,6 +4051,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
@@ -4051,7 +4258,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);
@@ -4093,7 +4305,58 @@ 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
+             && !callback_is_special_cased (e->callee->decl, e->call_stmt))
+           {
+             int ncallbacks = 0;
+             int nfound_edges = 0;
+             for (tree cb = lookup_attribute (CALLBACK_ATTR_IDENT, 
DECL_ATTRIBUTES (
+                                                            e->callee->decl));
+                  cb; cb = lookup_attribute (CALLBACK_ATTR_IDENT, 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 deca564a8e3..1dba1f1736e 100644
--- a/gcc/cgraph.h
+++ b/gcc/cgraph.h
@@ -1756,6 +1756,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:
 
@@ -1977,6 +2002,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 c160e8b6985..86e89d1cb74 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/fortran/f95-lang.cc b/gcc/fortran/f95-lang.cc
index bb4ce6d8288..496798a572f 100644
--- a/gcc/fortran/f95-lang.cc
+++ b/gcc/fortran/f95-lang.cc
@@ -580,6 +580,8 @@ 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_OACC_LIST (ECF_CB_2_4 | ATTR_NOTHROW_LIST)
 #define ATTR_PURE_NOTHROW_LIST (ECF_PURE | ECF_NOTHROW)
 
 static void
diff --git a/gcc/ipa-cp.cc b/gcc/ipa-cp.cc
index 3e073af662a..c806ece0b25 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.  */
 
@@ -6197,6 +6197,69 @@ 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_ATTR_IDENT,
+                                    DECL_ATTRIBUTES (e->callee->decl))
+                 && !callback_is_special_cased (e->callee->decl, e->call_stmt))
+               {
+                 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.  */
 
@@ -6227,6 +6290,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..b95a2a8381d 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..c6ab256859d 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 07a10244002..4be2cc6abe3 100644
--- a/gcc/ipa-inline-transform.cc
+++ b/gcc/ipa-inline-transform.cc
@@ -780,7 +780,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 a960d55b661..50c50dd8b75 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..bcf2b820294 100644
--- a/gcc/ipa-param-manipulation.cc
+++ b/gcc/ipa-param-manipulation.cc
@@ -50,6 +50,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "sreal.h"
 #include "ipa-cp.h"
 #include "ipa-prop.h"
+#include "attr-callback.h"
 
 /* Actual prefixes of different newly synthetized parameters.  Keep in sync
    with IPA_PARAM_PREFIX_* defines.  */
@@ -308,6 +309,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_ATTR_IDENT, 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 +499,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 +530,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 +550,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 +571,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 84d4fb5db67..bd5ac871027 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.  */
 
@@ -2416,6 +2422,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.  */
@@ -2441,6 +2459,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);
@@ -2519,10 +2538,58 @@ 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_ATTR_IDENT,
+                                       DECL_ATTRIBUTES (cs->callee->decl));
+                 for (; callback_attr;
+                      callback_attr
+                      = lookup_attribute (CALLBACK_ATTR_IDENT,
+                                          TREE_CHAIN (callback_attr)))
+                   if (callback_get_fn_index (callback_attr) == n)
+                     break;
+
+                 /* If no callback attribute is found, check if the function is
+                    a special case.  */
+                 if (!callback_attr
+                     && callback_is_special_cased (cs->callee->decl, call))
+                   {
+                     callback_attr
+                       = callback_special_case_attr (cs->callee->decl);
+                     /* Check if the special attribute describes the correct
+                        attribute, as a special cased function might have
+                        multiple callbacks.  */
+                     if (callback_get_fn_index (callback_attr) != n)
+                       callback_attr = NULL;
+                   }
+
+                 /* If a callback attribute describing this pointer is found,
+                             create a callback edge to the 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)
        {
@@ -2580,6 +2647,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 ec34f659d6a..397df2fa4ba 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 db1ec963841..f3936fbcb19 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)
@@ -358,35 +358,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",
@@ -409,10 +409,10 @@ 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)
@@ -430,7 +430,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,
@@ -471,7 +471,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/ipa/ipcp-cb-spec1.c 
b/gcc/testsuite/gcc.dg/ipa/ipcp-cb-spec1.c
new file mode 100644
index 00000000000..a85e62300f9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/ipa/ipcp-cb-spec1.c
@@ -0,0 +1,19 @@
+/* Test that GOMP_task is special cased when cpyfn is NULL.  */
+
+/* { dg-do run } */
+/* { dg-options "-O3 -fopenmp -flto -std=gnu99 -fdump-ipa-cp-details" } */
+/* { dg-require-effective-target fopenmp } */
+/* { dg-require-effective-target lto } */
+
+void test(int c) {
+  for (int i = 0; i < c; i++)
+    if (!__builtin_constant_p(c))
+      __builtin_abort();
+}
+int main() {
+#pragma omp task
+  test(7);
+  return 0;
+}
+
+/* { dg-final { scan-wpa-ipa-dump "Creating a specialized node of 
main._omp_fn" "cp" } } */
diff --git a/gcc/testsuite/gcc.dg/ipa/ipcp-cb-spec2.c 
b/gcc/testsuite/gcc.dg/ipa/ipcp-cb-spec2.c
new file mode 100644
index 00000000000..01d7425c99f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/ipa/ipcp-cb-spec2.c
@@ -0,0 +1,21 @@
+/* Check that GOMP_task doesn't produce callback edges when cpyfn is not
+   NULL.  */
+
+/* { dg-do run } */
+/* { dg-options "-O3 -fopenmp -flto -std=gnu99 -fdump-ipa-cp-details" } */
+/* { dg-require-effective-target fopenmp } */
+/* { dg-require-effective-target lto } */
+
+void test(int *a) {
+  for (int i = 0; i < 100; i++) {
+    a[i] = i;
+  }
+}
+int main() {
+  int a[100];
+  __builtin_memset (a, 0, sizeof (a));
+  #pragma omp task
+  test (a);
+}
+
+/* { dg-final { scan-ipa-dump-not "Created callback edge" "cp" } } */
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..3418b5dedab
--- /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/tree-core.h b/gcc/tree-core.h
index 028b6af1fdb..4780881fd50 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -98,6 +98,14 @@ 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, 2) */
+#define ECF_CB_1_2      (1 << 17)
+
+/* callback(2, 4) */
+#define ECF_CB_2_4      (1 << 18)
+
 /* 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 dee2dfc2620..f076a2c80e4 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,
@@ -3050,8 +3063,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 c8b8b3edd35..586e445a72d 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -73,6 +73,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "dfp.h"
 #include "asan.h"
 #include "ubsan.h"
+#include "attr-callback.h"
 
 /* Names of tree components.
    Used for printing out the tree and error messages.  */
@@ -9914,7 +9915,22 @@ set_call_expr_flags (tree decl, int flags)
     DECL_ATTRIBUTES (decl)
       = tree_cons (get_identifier ("expected_throw"),
                   NULL, DECL_ATTRIBUTES (decl));
-  /* Looping const or pure is implied by noreturn.
+
+  if (flags & ECF_CB_1_2)
+    {
+      tree attr = callback_build_attr (1, 1, 2);
+      TREE_CHAIN (attr) = DECL_ATTRIBUTES (decl);
+      DECL_ATTRIBUTES (decl) = attr;
+    }
+
+  if (flags & ECF_CB_2_4)
+    {
+      tree attr = callback_build_attr (2, 1, 4);
+      TREE_CHAIN (attr) = DECL_ATTRIBUTES (decl);
+      DECL_ATTRIBUTES (decl) = attr;
+    }
+
+    /* 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)
              || ((flags & ECF_NORETURN) && (flags & (ECF_CONST | ECF_PURE))));
-- 
2.50.0

Reply via email to