[gcc r17-3013] Add the callback_only attribute.

Josef Melcr via Gcc-cvs <[email protected]> Thu, 6 Aug 2026 09:13:28 +0000 (GMT)
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:cad654efbba62da3d98d9ca6a4c1658fcd47ffb4

commit r17-3013-gcad654efbba62da3d98d9ca6a4c1658fcd47ffb4
Author: Josef Melcr <[email protected]>
Date:   Mon Jul 27 16:18:54 2026 +0200

    Add the callback_only attribute.
    
    This patch allows the callback attribute to be used in user programs
    under the name 'callback_only'.   It moves the attribute handler into
    c-attribs.cc and renames some of the identifiers from callback to
    callback_only for the sake of consistency.
    
    gcc/ChangeLog:
    
            * attr-callback.cc (callback_build_attr): Use "callback_only"
            instead of the macro.
            (callback_edge_callee_has_attr): Likewise.
            (callback_fetch_attr_by_edge): Likewise.
            (get_nth_list_elem): Move to c-family/c-attribs.cc.
            (handle_callback_attribute): Rename to
            handle_callback_only_attribute, move to c-family/c-attribs.cc.
            * attr-callback.h (CALLBACK_ATTR_IDENT): Remove the macro.
            (handle_callback_attribute): Remove the declaration.
            * builtin-attrs.def (ATTR_CALLBACK): Rename to
            ATTR_CALLBACK_ONLY.
            (ATTR_CALLBACK_ONLY): Identifier for the attribute for builtins.
            (DEF_CALLBACK_ATTRIBUTE): Rename to DEF_CALLBACK_ONLY_ATTRIBUTE.
            (DEF_CALLBACK_ONLY_ATTRIBUTE): Renamed definition macro for builtins.
            (GOMP): Use the renamed macro.
            (ATTR_CALLBACK_GOMP_LIST): Renamed to
            ATTR_CALLBACK_ONLY_GOMP_LIST.
            (ATTR_CALLBACK_ONLY_GOMP_LIST): ATTR_NOTHROW_LIST with the
            callback_only attribute for libgomp functions.
            * cgraph.cc (cgraph_edge::redirect_call_stmt_to_callee): Use
            "callback_only" instead of the macro.
            (cgraph_node::verify_node): Likewise.
            * doc/extend.texi: Add callback_only attribute docs.
            * ipa-cp.cc (purge_useless_callback_edges): Use "callback_only"
            instead of the macro.
            * ipa-param-manipulation.cc
            (drop_decl_attribute_if_params_changed_p): Likewise.
            * ipa-prop.cc (ipa_compute_jump_functions_for_edge): Likewise.
            * omp-builtins.def (BUILT_IN_GOMP_PARALLEL_LOOP_STATIC): Use the
            renamed attribute list.
            (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: Update the ECF_CB flag comment.
    
    gcc/c-family/ChangeLog:
    
            * c-attribs.cc (handle_callback_only_attribute): Moved from
            attr-callback.cc.
            (get_nth_list_elem): Likewise.
    
    gcc/fortran/ChangeLog:
    
            * f95-lang.cc (ATTR_CALLBACK_GOMP_LIST): Rename to
            ATTR_CALLBACK_ONLY_GOMP_LIST.
            (ATTR_CALLBACK_ONLY_GOMP_LIST): Fortran counterpart for the
            attribute list in builtin-attrs.def.
    
    gcc/testsuite/ChangeLog:
    
            * gcc.dg/attr-callback.c: New test.
            * gcc.dg/ipa/ipcp-cb2.c: New test.
    
    Signed-off-by: Josef Melcr <[email protected]>

Diff:
---
 gcc/attr-callback.cc                 | 187 +----------------------------------
 gcc/attr-callback.h                  |   7 --
 gcc/builtin-attrs.def                |  18 ++--
 gcc/c-family/c-attribs.cc            | 184 +++++++++++++++++++++++++++++++++-
 gcc/cgraph.cc                        |  11 ++-
 gcc/doc/extend.texi                  |  39 ++++++++
 gcc/fortran/f95-lang.cc              |   2 +-
 gcc/ipa-cp.cc                        |   2 +-
 gcc/ipa-param-manipulation.cc        |   2 +-
 gcc/ipa-prop.cc                      |   4 +-
 gcc/omp-builtins.def                 |  26 ++---
 gcc/testsuite/gcc.dg/attr-callback.c | 107 ++++++++++++++++++++
 gcc/testsuite/gcc.dg/ipa/ipcp-cb2.c  |  60 +++++++++++
 gcc/tree-core.h                      |   2 +-
 14 files changed, 427 insertions(+), 224 deletions(-)

diff --git a/gcc/attr-callback.cc b/gcc/attr-callback.cc
index c654d74164db..19a8770707b4 100644
--- a/gcc/attr-callback.cc
+++ b/gcc/attr-callback.cc
@@ -54,7 +54,7 @@ callback_build_attr (unsigned fn_idx, unsigned arg_count...)
   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);
+    = tree_cons (get_identifier ("callback_only"), cblist, NULL_TREE);
   return attr;
 }
 
@@ -87,7 +87,7 @@ callback_special_case_attr (tree decl)
 bool
 callback_edge_callee_has_attr (cgraph_edge *e)
 {
-  return lookup_attribute (CALLBACK_ATTR_IDENT,
+  return lookup_attribute ("callback_only",
 			   DECL_ATTRIBUTES (e->callee->decl))
 	 || callback_is_special_cased (e->callee->decl, e->call_stmt);
 }
@@ -113,12 +113,12 @@ callback_fetch_attr_by_edge (cgraph_edge *e, cgraph_edge *carrying)
   if (callback_is_special_cased (carrying->callee->decl, e->call_stmt))
     return callback_special_case_attr (carrying->callee->decl);
 
-  tree cb_attr = lookup_attribute (CALLBACK_ATTR_IDENT,
+  tree cb_attr = lookup_attribute ("callback_only",
 				   DECL_ATTRIBUTES (carrying->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)))
+       cb_attr = lookup_attribute ("callback_only", TREE_CHAIN (cb_attr)))
     {
       unsigned id = callback_get_fn_index (cb_attr);
       if (id == e->callback_id)
@@ -167,185 +167,6 @@ callback_fetch_fn_position (cgraph_edge *e, cgraph_edge *carrying)
   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.  */
-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.  */
-  int callback_fn_idx = TREE_INT_CST_LOW (cb_fn_idx_node);
-  tree decl_type_args = TYPE_ARG_TYPES (TREE_TYPE (decl));
-  tree it;
-  int 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;
-	}
-
-      int 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;
-	}
-    }
-
-  /* Check that the decl does not already have a callback attribute describing
-     the same argument.  */
-  it = lookup_attribute (CALLBACK_ATTR_IDENT, DECL_ATTRIBUTES (decl));
-  for (; it; it = lookup_attribute (CALLBACK_ATTR_IDENT, TREE_CHAIN (it)))
-    if (callback_get_fn_index (it) == callback_fn_idx)
-      {
-	error_at (DECL_SOURCE_LOCATION (decl),
-		  "function declaration has multiple callback attributes "
-		  "describing argument no. %d",
-		  callback_fn_idx + 1);
-	*no_add_attrs = true;
-	break;
-      }
-
-  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.  */
diff --git a/gcc/attr-callback.h b/gcc/attr-callback.h
index b2c1c3c09c5d..5ed93b3afefc 100644
--- a/gcc/attr-callback.h
+++ b/gcc/attr-callback.h
@@ -28,8 +28,6 @@ enum callback_position
   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.  */
 tree callback_build_attr (unsigned fn_idx, unsigned arg_count...);
@@ -64,11 +62,6 @@ auto_vec<int> callback_get_arg_mapping (cgraph_edge *e, cgraph_edge *carrying);
    E's callee in the argument list of CARRYING's callee decl.  */
 int callback_fetch_fn_position (cgraph_edge *e, cgraph_edge *carrying);
 
-/* Handle a "callback" attribute; arguments as in
-   struct attribute_spec.handler.  */
-tree handle_callback_attribute (tree *node, tree name, tree args, int flags,
-				bool *no_add_attrs);
-
 /* 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.  */
diff --git a/gcc/builtin-attrs.def b/gcc/builtin-attrs.def
index be80184b1a16..e7458ed01b06 100644
--- a/gcc/builtin-attrs.def
+++ b/gcc/builtin-attrs.def
@@ -130,7 +130,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_IDENT (ATTR_CALLBACK_ONLY, "callback_only")
 
 DEF_ATTR_TREE_LIST (ATTR_NOVOPS_LIST, ATTR_NOVOPS, ATTR_NULL, ATTR_NULL)
 
@@ -431,15 +431,15 @@ 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)
+/* Construct callback_only attributes for GOMP builtins.  */
+#define DEF_CALLBACK_ONLY_ATTRIBUTE(TYPE, CA, VALUES)			 \
+  DEF_ATTR_TREE_LIST (ATTR_CALLBACK_ONLY_##TYPE##_##CA##_##VALUES,       \
+		      ATTR_CALLBACK_ONLY, ATTR_##CA, ATTR_LIST_##VALUES)
 
-DEF_CALLBACK_ATTRIBUTE(GOMP, 1, 2)
-DEF_ATTR_TREE_LIST(ATTR_CALLBACK_GOMP_LIST, ATTR_CALLBACK,
-                   ATTR_CALLBACK_GOMP_1_2, ATTR_NOTHROW_LIST)
-#undef DEF_CALLBACK_ATTRIBUTE
+DEF_CALLBACK_ONLY_ATTRIBUTE(GOMP, 1, 2)
+DEF_ATTR_TREE_LIST(ATTR_CALLBACK_ONLY_GOMP_LIST, ATTR_CALLBACK_ONLY,
+                   ATTR_CALLBACK_ONLY_GOMP_1_2, ATTR_NOTHROW_LIST)
+#undef DEF_CALLBACK_ONLY_ATTRIBUTE
 
 /* Transactional memory variants of the above.  */
 
diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
index d668ab96630a..72702b35aedb 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -131,6 +131,7 @@ static tree handle_no_limit_stack_attribute (tree *, tree, tree, int,
 static tree handle_pure_attribute (tree *, tree, tree, int, bool *);
 static tree handle_tm_attribute (tree *, tree, tree, int, bool *);
 static tree handle_tm_wrap_attribute (tree *, tree, tree, int, bool *);
+static tree handle_callback_only_attribute (tree *, tree, tree, int, bool *);
 static tree handle_novops_attribute (tree *, tree, tree, int, bool *);
 static tree handle_unavailable_attribute (tree *, tree, tree, int,
 					  bool *);
@@ -485,8 +486,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 },
+  { "callback_only",          1, -1, true, false, false, false,
+			      handle_callback_only_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,
@@ -4672,6 +4673,185 @@ handle_tm_wrap_attribute (tree *node, tree name, tree args,
   return NULL_TREE;
 }
 
+/* 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_only" attribute; arguments as in
+   struct attribute_spec.handler.  */
+tree
+handle_callback_only_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.  */
+  int callback_fn_idx = TREE_INT_CST_LOW (cb_fn_idx_node);
+  tree decl_type_args = TYPE_ARG_TYPES (TREE_TYPE (decl));
+  tree it;
+  int 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;
+	}
+
+      int 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;
+	}
+    }
+
+  /* Check that the decl does not already have a callback attribute describing
+     the same argument.  */
+  it = lookup_attribute ("callback_only", DECL_ATTRIBUTES (decl));
+  for (; it; it = lookup_attribute ("callback_only", TREE_CHAIN (it)))
+    if (callback_get_fn_index (it) == callback_fn_idx)
+      {
+	error_at (DECL_SOURCE_LOCATION (decl),
+		  "function declaration has multiple callback attributes "
+		  "describing argument no. %d",
+		  callback_fn_idx + 1);
+	*no_add_attrs = true;
+	break;
+      }
+
+  return NULL_TREE;
+}
+
 /* Ignore the given attribute.  Used when this attribute may be usefully
    overridden by the target, but is not used generically.  */
 
diff --git a/gcc/cgraph.cc b/gcc/cgraph.cc
index 4b22cad9f116..da770ccc4af7 100644
--- a/gcc/cgraph.cc
+++ b/gcc/cgraph.cc
@@ -1844,7 +1844,7 @@ cgraph_edge::redirect_call_stmt_to_callee (cgraph_edge *e,
     {
       cgraph_edge *carrying = e->get_callback_carrying_edge ();
       if (!callback_is_special_cased (carrying->callee->decl, e->call_stmt)
-	  && !lookup_attribute (CALLBACK_ATTR_IDENT,
+	  && !lookup_attribute ("callback_only",
 				DECL_ATTRIBUTES (carrying->callee->decl)))
 	/* Callback attribute is removed if the dispatching function changes
 	   signature, as the indices wouldn't be correct anymore.  These edges
@@ -4420,10 +4420,11 @@ cgraph_node::verify_node (void)
 	    {
 	      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 (tree cb
+		   = lookup_attribute ("callback_only",
+				       DECL_ATTRIBUTES (e->callee->decl));
+		   cb; cb = lookup_attribute ("callback_only", TREE_CHAIN (cb)),
+		       ncallbacks++)
 		;
 	      for (cgraph_edge *cbe = callees; cbe; cbe = cbe->next_callee)
 		{
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 9a3f158792d7..225f75e7217e 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -2354,6 +2354,45 @@ Note that the BTF format currently only has a representation for type
 tags associated with pointer types.  Type tags on non-pointer types
 may be silently skipped when generating BTF.
 
+@atindex @code{callback_only}
+@cindex functions with callbacks
+@item callback_only
+The @code{callback_only} attribute specifies that the annotated function may
+call the specified callback function. The first parameter identifies the index
+of the callback function, the rest of the arguments specify the indices of the
+arguments of the indirect call. All indices start from 1. If the function takes
+the implicit @code{this} pointer, it is referred to by the index 1, with the
+rest of the arguments starting at index 2. The index 0 marks an argument not
+present in the arguments of the annotated function, an argument which is
+modified before calling the callback function. The annotated function must pass
+the specified arguments in the specified order to the callback function, which
+must be callable with the number, order and type of the arguments. The
+specified pointer to the callback may not escape the translation unit of the
+annotated function and it may not be captured. The annotated function is
+required to pass the arguments through, it may not change or dereference them.
+The arguments also may not escape. The attribute may be used multiple times per
+function, though only one @code{callback_only} attribute may be used per
+function parameter.
+
+The attribute exposes the potentially hidden callsite in the annotated
+function, enabling interprocedural optimizations which may not be possible
+without the attribute. It is most useful for annotating functions from
+dynamically linked libraries, as their bodies are not available during
+compilation.
+
+This attribute is similar to the clang @code{callback} attribute but it is not
+compatible with it. The clang implementation allows identifiers as arguments,
+marks an unknown argument with -1 and the @code{this} pointer with the index 0.
+
+An example usage:
+
+@smallexample
+[[gnu::callback_only(1, 3, 2)]]
+void foo(void (*bar)(int*, double*), double* y, int* x);
+@end smallexample
+
+means that there is a call @code{bar (x, y)} inside @code{foo}.
+
 @atindex @code{cleanup}
 @cindex cleanup functions
 @item cleanup (@var{cleanup_function})
diff --git a/gcc/fortran/f95-lang.cc b/gcc/fortran/f95-lang.cc
index b796bcf142d7..bfc2651c0767 100644
--- a/gcc/fortran/f95-lang.cc
+++ b/gcc/fortran/f95-lang.cc
@@ -582,7 +582,7 @@ 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_ONLY_GOMP_LIST (ECF_CB_1_2 | 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 431da4989215..d2e3254643b6 100644
--- a/gcc/ipa-cp.cc
+++ b/gcc/ipa-cp.cc
@@ -6477,7 +6477,7 @@ purge_useless_callback_edges ()
 	      if (dump_file)
 		fprintf (dump_file, "\tExamining callbacks of edge %s -> %s:\n",
 			 e->caller->dump_name (), e->callee->dump_name ());
-	      if (!lookup_attribute (CALLBACK_ATTR_IDENT,
+	      if (!lookup_attribute ("callback_only",
 				     DECL_ATTRIBUTES (e->callee->decl))
 		  && !callback_is_special_cased (e->callee->decl, e->call_stmt))
 		{
diff --git a/gcc/ipa-param-manipulation.cc b/gcc/ipa-param-manipulation.cc
index 6f85d10a29e1..0f384848ce55 100644
--- a/gcc/ipa-param-manipulation.cc
+++ b/gcc/ipa-param-manipulation.cc
@@ -314,7 +314,7 @@ drop_type_attribute_if_params_changed_p (tree name)
 static bool
 drop_decl_attribute_if_params_changed_p (tree name)
 {
-  if (is_attribute_p (CALLBACK_ATTR_IDENT, name))
+  if (is_attribute_p ("callback_only", name))
     return true;
   return false;
 }
diff --git a/gcc/ipa-prop.cc b/gcc/ipa-prop.cc
index b0b078b9cd21..e66c84ebcdf4 100644
--- a/gcc/ipa-prop.cc
+++ b/gcc/ipa-prop.cc
@@ -2595,11 +2595,11 @@ ipa_compute_jump_functions_for_edge (struct ipa_func_body_info *fbi,
 		  /* Argument is a pointer to a function. Look for a callback
 		     attribute describing this argument.  */
 		  tree callback_attr
-		    = lookup_attribute (CALLBACK_ATTR_IDENT,
+		    = lookup_attribute ("callback_only",
 					DECL_ATTRIBUTES (cs->callee->decl));
 		  for (; callback_attr;
 		       callback_attr
-		       = lookup_attribute (CALLBACK_ATTR_IDENT,
+		       = lookup_attribute ("callback_only",
 					   TREE_CHAIN (callback_attr)))
 		    if (callback_get_fn_index (callback_attr) == n)
 		      break;
diff --git a/gcc/omp-builtins.def b/gcc/omp-builtins.def
index 500cc32ab4d4..192ff414e917 100644
--- a/gcc/omp-builtins.def
+++ b/gcc/omp-builtins.def
@@ -368,35 +368,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_CALLBACK_GOMP_LIST)
+		  ATTR_CALLBACK_ONLY_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_CALLBACK_GOMP_LIST)
+		  ATTR_CALLBACK_ONLY_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_CALLBACK_GOMP_LIST)
+		  ATTR_CALLBACK_ONLY_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_CALLBACK_GOMP_LIST)
+		  ATTR_CALLBACK_ONLY_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_CALLBACK_GOMP_LIST)
+		  ATTR_CALLBACK_ONLY_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_CALLBACK_GOMP_LIST)
+		  ATTR_CALLBACK_ONLY_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_CALLBACK_GOMP_LIST)
+		  ATTR_CALLBACK_ONLY_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_CALLBACK_GOMP_LIST)
+		  ATTR_CALLBACK_ONLY_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",
@@ -419,10 +419,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_CALLBACK_GOMP_LIST)
+		  BT_FN_VOID_OMPFN_PTR_UINT_UINT, ATTR_CALLBACK_ONLY_GOMP_LIST)
 DEF_GOMP_BUILTIN (BUILT_IN_GOMP_PARALLEL_REDUCTIONS,
 		  "GOMP_parallel_reductions",
-		  BT_FN_UINT_OMPFN_PTR_UINT_UINT, ATTR_CALLBACK_GOMP_LIST)
+		  BT_FN_UINT_OMPFN_PTR_UINT_UINT, ATTR_CALLBACK_ONLY_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)
@@ -440,7 +440,8 @@ 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_CALLBACK_GOMP_LIST)
+		  BT_FN_VOID_OMPFN_PTR_UINT_UINT_UINT,
+		  ATTR_CALLBACK_ONLY_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,
@@ -490,7 +491,8 @@ 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_CALLBACK_GOMP_LIST)
+		  BT_FN_VOID_OMPFN_PTR_UINT_UINT_UINT,
+		  ATTR_CALLBACK_ONLY_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 100755
index 000000000000..d9cc67c3affe
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-callback.c
@@ -0,0 +1,107 @@
+/* Test callback attribute error checking. */
+
+/* { dg-do compile } */
+/* { dg-options "-std=gnu17 -Wattributes" } */
+
+[[gnu::callback_only(1, 2)]]
+void
+correct_1(void (*)(int*), int*);
+
+[[gnu::callback_only(1, 2, 3)]]
+void
+correct_2(void (*)(int*, double*), int*, double*);
+
+[[gnu::callback_only(1, 2, 3), gnu::callback_only(4, 5)]]
+void
+correct_3(void (*)(int*, double*), int*, double*, int (*)(void*), void*);
+
+[[gnu::callback_only(1, 0)]]
+void
+unknown_1(void (*)(int*));
+
+[[gnu::callback_only(1, 2, 0)]]
+void
+unknown_2(void (*)(int*, double*), int*, double*, char*);
+
+[[gnu::callback_only(1, 0, 3, 3)]]
+void
+too_many(void (*)(int*, double*), int*, double*); /* { dg-error "argument number mismatch, 2 expected, got 3" }*/
+
+[[gnu::callback_only(1, 2)]]
+void
+too_few_1(void (*)(int*, double*), int*, double*); /* { dg-error "argument number mismatch, 2 expected, got 1" }*/
+
+[[gnu::callback_only(1)]]
+void
+too_few_2(void (*)(int*, double*), int*, double*); /* { dg-error "argument number mismatch, 2 expected, got 0" }*/
+
+[[gnu::callback_only(3, 1)]]
+void
+promotion(char*, float, int (*)(int*));
+
+[[gnu::callback_only(2, 3)]]
+void
+downcast(char*, void* (*)(float*), double*);
+
+[[gnu::callback_only(1, 2, 5)]]
+void
+out_of_range_1(char (*)(float*, double*), float*, double*, int*); /* { dg-error "callback argument index 5 is out of range" } */
+
+[[gnu::callback_only(1, -2, 3)]]
+void
+out_of_range_2(char (*)(float*, double*), float*, double*, int*); /* { dg-error "callback argument index -2 is out of range" } */
+
+[[gnu::callback_only(-1, 2, 3)]]
+void
+out_of_range_3(char (*)(float*, double*), float*, double*, int*); /* { dg-error "callback function index -1 is out of range" } */
+
+[[gnu::callback_only(67, 2, 3)]]
+void
+out_of_range_4(char (*)(float*, double*), float*, double*, int*); /* { dg-error "callback function index 67 is out of range" } */
+
+[[gnu::callback_only(0, 2, 3)]]
+void
+unknown_fn(char (*)(float*, double*), float*, double*, int*); /* { dg-error "callback function position cannot be marked as unknown" } */
+
+[[gnu::callback_only(1, 2)]]
+void
+not_a_fn(int, int); /* { dg-error "argument no. 1 is not an address of a function" } */
+
+struct S
+{
+  int x;
+};
+
+[[gnu::callback_only(1, 2)]]
+void
+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" } */
+
+[[gnu::callback_only(1, 3, 2)]]
+void
+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" } */
+
+[[gnu::callback_only(1, "2")]]
+void
+wrong_arg_type_1(void (*)(void*), void*); /* { dg-error "argument no. 1 is not an integer constant" } */
+
+[[gnu::callback_only("not a number", 2, 2)]]
+void
+wrong_arg_type_2(void (*)(void*, void*), void*); /* { dg-error "argument specifying callback function position is not an integer constant" } */
+
+[[gnu::callback_only(1, 2), gnu::callback_only(1, 3)]]
+void
+multiple_single_fn(void (*)(int*), int*, int*); /* { dg-error "function declaration has multiple callback attributes describing argument no. 1" } */
+
+/* Check that the attribute won't resolve outside of our namespace.  */
+
+[[callback(1, 2)]] /* { dg-warning "ignored" } */
+void
+ignore_1(void (*)(int*), int*);
+
+[[gnu::callback(1, 2)]]
+void
+ignore_2(void (*)(int*), int*); /* { dg-warning "ignored" } */
+
+[[clang::callback_only(1, 2)]]
+void
+ignore_3(void (*)(int*), int*); /* { dg-warning "ignored" } */
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 000000000000..201d41d48f3c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/ipa/ipcp-cb2.c
@@ -0,0 +1,60 @@
+/* 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-do compile } */
+/* { dg-options "-O3 -fdump-ipa-cp" } */
+
+struct S
+{
+  int a, b, c;
+};
+
+extern void *blah (int, void *);
+
+[[gnu::callback_only (1, 2), gnu::callback_only (3, 4, 5)]] extern 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-ipa-dump "Creating a specialized node of cb1" "cp" } } */
+/* { dg-final { scan-ipa-dump "Creating a specialized node of cb2" "cp" } } */
+/* { dg-final { scan-ipa-dump-times "Aggregate replacements: " 2 "cp" } } */
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 3ac0d56fc990..1687b1f94672 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -103,7 +103,7 @@ class irange;
    meant to be used for the construction of builtin functions.  They were only
    added because Fortran uses them for attributes of builtins.  */
 
-/* callback(1, 2) */
+/* callback_only (1, 2) */
 #define ECF_CB_1_2		  (1 << 17)
 
 /* Call argument flags.  */