Hi,
this is a v3 of this patch (v2:
https://gcc.gnu.org/pipermail/gcc-patches/2026-May/715612.html)
Sorry about the formatting issues, formatting is the bane of my
existence for some reason.
I did not retest the patch, as I only made formatting changes.
Changes in v3:
- Fixed formatting
Changes in v2:
- Documentation tweaks suggested by Andrew
This patch allows the callback attribute to be used outside of GCC under
the name 'gnu::callback_only'. It also fixes some missing bounds checks
in the attribute handler. The attribute is not recognized outside of
the gnu namespace, I did this so that there is no accidental mixup with
the LLVM callback attribute, which has different syntax. I am not great
at user-facing documentation, I hope the docs are clear about the
attribute's usage. Suggestions are of course welcome.
OK for master?
Best regards,
Josef
gcc/ChangeLog:
* attr-callback.cc (callback_edge_callee_has_attr): Specify gnu
namespace in attribute lookup.
(callback_fetch_attr_by_edge): Likewise.
(handle_callback_attribute): Fix bugs in bounds checks, add gnu
namespace to attribute lookup.
* attr-callback.h (CALLBACK_ATTR_IDENT): Change identifier to
'callback_only'.
* builtin-attrs.def (ATTR_CALLBACK): Likewise.
* cgraph.cc (cgraph_edge::redirect_call_stmt_to_callee): Specify
gnu namespace in attribute lookup.
(cgraph_node::verify_node): Likewise.
* doc/extend.texi: Add gnu::callback_only attribute
documentation.
* ipa-cp.cc (purge_useless_callback_edges): Specify gnu
namespace in attribute lookup.
* ipa-prop.cc (ipa_compute_jump_functions_for_edge): Likewise.
* tree-core.h: Change ECF_CB_* comment from 'callback' to
'callback_only'.
gcc/c-family/ChangeLog:
* c-attribs.cc: Define the 'callback_only' attribute in gnu
namespace only.
* c-common.h: Add extern decl for the callback attribute table.
gcc/c/ChangeLog:
* c-objc-common.h: Add the callback attribute table to allow it
to be registered.
gcc/cp/ChangeLog:
* cp-objcp-common.h: Likewise.
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]>
---
gcc/attr-callback.cc | 20 ++---
gcc/attr-callback.h | 2 +-
gcc/builtin-attrs.def | 2 +-
gcc/c-family/c-attribs.cc | 13 +++-
gcc/c-family/c-common.h | 1 +
gcc/c/c-objc-common.h | 3 +-
gcc/cgraph.cc | 12 +--
gcc/cp/cp-objcp-common.h | 1 +
gcc/doc/extend.texi | 39 ++++++++++
gcc/ipa-cp.cc | 2 +-
gcc/ipa-prop.cc | 4 +-
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, 245 insertions(+), 23 deletions(-)
create mode 100755 gcc/testsuite/gcc.dg/attr-callback.c
create mode 100644 gcc/testsuite/gcc.dg/ipa/ipcp-cb2.c
diff --git a/gcc/attr-callback.cc b/gcc/attr-callback.cc
index c654d74164d..041e2463592 100644
--- a/gcc/attr-callback.cc
+++ b/gcc/attr-callback.cc
@@ -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 ("gnu", CALLBACK_ATTR_IDENT,
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 ("gnu", CALLBACK_ATTR_IDENT,
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)))
+ for (; cb_attr; cb_attr = lookup_attribute ("gnu", CALLBACK_ATTR_IDENT,
+ TREE_CHAIN (cb_attr)))
{
unsigned id = callback_get_fn_index (cb_attr);
if (id == e->callback_id)
@@ -229,10 +229,11 @@ handle_callback_attribute (tree *node, tree name, tree
args,
return NULL_TREE;
}
--callback_fn_idx;
- if (callback_fn_idx >= decl_nargs)
+ if (callback_fn_idx < 0 || callback_fn_idx >= decl_nargs)
{
error_at (DECL_SOURCE_LOCATION (decl),
- "callback function position out of range");
+ "callback function index %d is out of range",
+ callback_fn_idx + 1);
*no_add_attrs = true;
return NULL_TREE;
}
@@ -305,7 +306,7 @@ handle_callback_attribute (tree *node, tree name, tree args,
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)
+ if (arg_idx < 0 || arg_idx >= decl_nargs)
{
error_at (DECL_SOURCE_LOCATION (decl),
"callback argument index %d is out of range", arg_idx + 1);
@@ -331,8 +332,9 @@ handle_callback_attribute (tree *node, tree name, tree args,
/* 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)))
+ it = lookup_attribute ("gnu", CALLBACK_ATTR_IDENT, DECL_ATTRIBUTES (decl));
+ for (; it;
+ it = lookup_attribute ("gnu", CALLBACK_ATTR_IDENT, TREE_CHAIN (it)))
if (callback_get_fn_index (it) == callback_fn_idx)
{
error_at (DECL_SOURCE_LOCATION (decl),
diff --git a/gcc/attr-callback.h b/gcc/attr-callback.h
index b2c1c3c09c5..e51c4c6c571 100644
--- a/gcc/attr-callback.h
+++ b/gcc/attr-callback.h
@@ -28,7 +28,7 @@ enum callback_position
CB_UNKNOWN_POS = 0
};
-#define CALLBACK_ATTR_IDENT " callback"
+#define CALLBACK_ATTR_IDENT "callback_only"
/* Returns a callback attribute with callback index FN_IDX, and ARG_COUNT
arguments specified by VA_ARGS. */
diff --git a/gcc/builtin-attrs.def b/gcc/builtin-attrs.def
index be80184b1a1..a395f454be1 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, "callback_only")
DEF_ATTR_TREE_LIST (ATTR_NOVOPS_LIST, ATTR_NOVOPS, ATTR_NULL, ATTR_NULL)
diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
index d437c55285e..598acc70d9f 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -485,8 +485,6 @@ const struct attribute_spec c_common_gnu_attributes[] =
handle_tm_attribute, NULL },
{ "transaction_may_cancel_outer", 0, 0, false, true, false, false,
handle_tm_attribute, NULL },
- { CALLBACK_ATTR_IDENT, 1, -1, true, false, false, false,
- handle_callback_attribute, NULL },
/* ??? These two attributes didn't make the transition from the
Intel language document to the multi-vendor language document. */
{ "transaction_pure", 0, 0, false, true, false, false,
@@ -707,6 +705,17 @@ const struct scoped_attribute_specs
c_common_format_attribute_table =
"gnu", { c_common_format_attributes }
};
+/* Attribute table for the callback attribute to be used by the C frontends.
+ We don't want to expose the attribute outside of the GNU namespace, so it
+ has to be separated out. */
+const struct attribute_spec c_common_callback_attribute[] = {
+ { CALLBACK_ATTR_IDENT, 1, -1, true, false, false, false,
+ handle_callback_attribute, NULL },
+};
+
+const struct scoped_attribute_specs c_common_callback_attribute_table
+ = { "gnu", { c_common_callback_attribute } };
+
/* Returns TRUE iff the attribute indicated by ATTR_ID takes a plain
identifier as an argument, so the front end shouldn't look it up. */
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index a4cd6786109..bcd23f098d0 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -832,6 +832,7 @@ extern struct visibility_flags visibility_options;
extern const struct scoped_attribute_specs c_common_gnu_attribute_table;
extern const struct scoped_attribute_specs c_common_clang_attribute_table;
extern const struct scoped_attribute_specs c_common_format_attribute_table;
+extern const struct scoped_attribute_specs c_common_callback_attribute_table;
/* Pointer to function to lazily generate the VAR_DECL for __FUNCTION__ etc.
ID is the identifier to use, NAME is the string.
diff --git a/gcc/c/c-objc-common.h b/gcc/c/c-objc-common.h
index e103646b67e..11f08a2f0ca 100644
--- a/gcc/c/c-objc-common.h
+++ b/gcc/c/c-objc-common.h
@@ -82,7 +82,8 @@ static const scoped_attribute_specs *const
c_objc_attribute_table[] =
&std_attribute_table,
&c_common_gnu_attribute_table,
&c_common_clang_attribute_table,
- &c_common_format_attribute_table
+ &c_common_format_attribute_table,
+ &c_common_callback_attribute_table
};
#undef LANG_HOOKS_ATTRIBUTE_TABLE
diff --git a/gcc/cgraph.cc b/gcc/cgraph.cc
index 353e8b498aa..85a717bf865 100644
--- a/gcc/cgraph.cc
+++ b/gcc/cgraph.cc
@@ -1843,7 +1843,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 ("gnu", CALLBACK_ATTR_IDENT,
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
@@ -4419,11 +4419,13 @@ 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 ("gnu", CALLBACK_ATTR_IDENT,
+ DECL_ATTRIBUTES (e->callee->decl));
+ cb; cb = lookup_attribute ("gnu", CALLBACK_ATTR_IDENT,
+ TREE_CHAIN (cb)), ncallbacks++)
;
+
for (cgraph_edge *cbe = callees; cbe; cbe = cbe->next_callee)
{
if (cbe->callback && cbe->call_stmt == e->call_stmt
diff --git a/gcc/cp/cp-objcp-common.h b/gcc/cp/cp-objcp-common.h
index ed29e65e4f3..4a4c6de1aea 100644
--- a/gcc/cp/cp-objcp-common.h
+++ b/gcc/cp/cp-objcp-common.h
@@ -131,6 +131,7 @@ static const scoped_attribute_specs *const
cp_objcp_attribute_table[] =
&c_common_gnu_attribute_table,
&c_common_clang_attribute_table,
&c_common_format_attribute_table,
+ &c_common_callback_attribute_table,
&internal_attribute_table
};
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 8dd0b3137bc..b6505e3914a 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -2357,6 +2357,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/ipa-cp.cc b/gcc/ipa-cp.cc
index 337fca71013..763ec9db8ae 100644
--- a/gcc/ipa-cp.cc
+++ b/gcc/ipa-cp.cc
@@ -6505,7 +6505,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 ("gnu", CALLBACK_ATTR_IDENT,
DECL_ATTRIBUTES (e->callee->decl))
&& !callback_is_special_cased (e->callee->decl, e->call_stmt))
{
diff --git a/gcc/ipa-prop.cc b/gcc/ipa-prop.cc
index cb89934ee38..f34ca56dd4f 100644
--- a/gcc/ipa-prop.cc
+++ b/gcc/ipa-prop.cc
@@ -2608,11 +2608,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 ("gnu", CALLBACK_ATTR_IDENT,
DECL_ATTRIBUTES (cs->callee->decl));
for (; callback_attr;
callback_attr
- = lookup_attribute (CALLBACK_ATTR_IDENT,
+ = lookup_attribute ("gnu", CALLBACK_ATTR_IDENT,
TREE_CHAIN (callback_attr)))
if (callback_get_fn_index (callback_attr) == n)
break;
diff --git a/gcc/testsuite/gcc.dg/attr-callback.c
b/gcc/testsuite/gcc.dg/attr-callback.c
new file mode 100755
index 00000000000..a45ad8464cd
--- /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_only(1, 2)]] /* { dg-warning "ignored" } */
+void
+ignore_1(void (*)(int*), int*);
+
+[[callback(1, 2)]] /* { dg-warning "ignored" } */
+void
+ignore_2(void (*)(int*), int*);
+
+[[gnu::callback(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 00000000000..201d41d48f3
--- /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 07e9318f5e8..0169c0d4c67 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -102,7 +102,7 @@ struct die_struct;
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. */
--
2.54.0