On Sun, Apr 27, 2025 at 2:58 AM Josef Melcr <melcr...@fit.cvut.cz> wrote:
>
> This patch enables constant propagation to outlined OpenMP kernels and
> improves support for optimizing callback functions in general. It
> implements the attribute 'callback' as found in clang, though argument
> numbering is a bit different, as described below. The title says OpenMP,
> but it can be used for any function which takes a callback argument, such
> as pthread functions, qsort and others.
>
> The attribute 'callback' captures the notion of a function calling one
> of its arguments with some of its parameters as arguments. An OpenMP
> example of such function is GOMP_parallel.
> We implement the attribute with new callgraph edges called 'callback'
> edges. They are imaginary edges pointing from the caller of the function
> with the attribute (e.g. caller of GOMP_parallel) to the body function
> itself (e.g. the outlined OpenMP body). They share their call statement
> with the edge from which they are derived (direct edge caller -> GOMP_parallel
> in this case). These edges allow passes such as ipa-cp to the see the
> hidden call site to the body function and optimize the function accordingly.
>
> To illustrate on an example, the body GOMP_parallel looks something
> like this:
>
> void GOMP_parallel (void (*fn) (void *), void *data, /* ... */)
> {
>   /* ... */
>   fn (data);
>   /* ... */
> }
>
>
> If we extend it with the attribute 'callback(1, 2)', we express that the
> function calls its first argument and passes it its second argument.
> This is represented in the call graph in this manner:
>
>              direct                         indirect
> caller -----------------> GOMP_parallel ---------------> fn
>   |
>   ----------------------> fn
>           callback
>
> The direct edge is then the parent edge, with all callback edges being
> the child edges.
> While constant propagation is the main focus of this patch, callback
> edges can be useful for different passes (for example, it improves icf
> for OpenMP kernels), as they allow for address redirection.
> If the outlined body function gets optimized and cloned, from body_fn to
> body_fn.optimized, the callback edge allows us to replace the
> address in the arguments list:
>
> GOMP_parallel (body_fn, &data_struct, /* ... */);
>
> becomes
>
> GOMP_parallel (body_fn.optimized, &data_struct, /* ... */);
>
> This redirection is possible for any function with the attribute.
>
> This callback attribute implementation is partially compatible with
> clang's implementation. Its semantics, arguments and argument indexing style 
> are
> the same, but we represent an unknown argument position with 0
> (precedent set by attributes such as 'format'), while clang uses -1 or '?'.
> We also allow for multiple callback attributes on the same function,
> while clang only allows one.
>
> The attribute allows us to propagate constants into body functions of
> OpenMP constructs. Currently, GCC won't propagate the value 'c' into the
> OpenMP body in the following example:
>
> int a[100];
> void test(int c) {
> #pragma omp parallel for
>   for (int i = 0; i < c; i++) {
>     if (!__builtin_constant_p(c)) {
>       __builtin_abort();
>     }
>     a[i] = i;
>   }
> }
> int main() {
>   test(100);
>   return a[5] - 5;
> }
>
> With this patch, the body function will get cloned and the constant 'c'
> will get propagated.
>
> Bootstrapped and regtested on x86_64-linux. OK for master?

This seems like it could also improve code dealing with C++ lambdas.
Have you thought of that?

Thanks,
Andrew


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

Reply via email to