aboutsummaryrefslogtreecommitdiff
path: root/gcc/attr-callback.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/attr-callback.cc')
-rw-r--r--gcc/attr-callback.cc367
1 files changed, 367 insertions, 0 deletions
diff --git a/gcc/attr-callback.cc b/gcc/attr-callback.cc
new file mode 100644
index 0000000..83d2754
--- /dev/null
+++ b/gcc/attr-callback.cc
@@ -0,0 +1,367 @@
+/* Callback attribute handling
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ Contributed by Josef Melcr <jmelcr@gcc.gnu.org>
+
+ 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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "alloc-pool.h"
+#include "cgraph.h"
+#include "diagnostic.h"
+#include "builtins.h"
+#include "options.h"
+#include "gimple-range.h"
+#include "attribs.h"
+#include "attr-callback.h"
+
+/* 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...)
+{
+ va_list args;
+ va_start (args, arg_count);
+
+ tree cblist = NULL_TREE;
+ tree *pp = &cblist;
+ unsigned i;
+ for (i = 0; i < arg_count; i++)
+ {
+ int num = va_arg (args, int);
+ tree tnum = build_int_cst (integer_type_node, num);
+ *pp = build_tree_list (NULL, tnum PASS_MEM_STAT);
+ pp = &TREE_CHAIN (*pp);
+ }
+ cblist
+ = tree_cons (NULL_TREE, build_int_cst (integer_type_node, fn_idx), cblist);
+ tree attr
+ = tree_cons (get_identifier (CALLBACK_ATTR_IDENT), cblist, NULL_TREE);
+ return attr;
+}
+
+/* Returns TRUE if a function should be treated as if it had a callback
+ attribute despite the DECL not having it. STMT can be passed NULL
+ if the call statement is not available at the time, for example WPA, but it
+ should be called with the statement itself whenever possible. */
+bool
+callback_is_special_cased (tree decl, gcall *stmt)
+{
+ if (fndecl_built_in_p (decl, BUILT_IN_GOMP_TASK))
+ {
+ if (stmt)
+ return gimple_call_arg (stmt, 2) == null_pointer_node;
+ return true;
+ }
+ return false;
+}
+
+/* Returns an attribute for a special cased function. */
+tree
+callback_special_case_attr (tree decl)
+{
+ if (fndecl_built_in_p (decl, BUILT_IN_GOMP_TASK))
+ return callback_build_attr (1, 1, 2);
+ gcc_unreachable ();
+}
+
+/* Given an instance of callback attribute, return the 0-based
+ index of the called function in question. */
+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;
+}
+
+/* For a given callback pair, retrieves the callback attribute used
+ to create E from the callee of CARRYING. */
+tree
+callback_fetch_attr_by_edge (cgraph_edge *e, cgraph_edge *carrying)
+{
+ gcc_checking_assert (e->call_stmt == carrying->call_stmt
+ && e->lto_stmt_uid == carrying->lto_stmt_uid);
+
+ 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,
+ 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)))
+ {
+ unsigned id = callback_get_fn_index (cb_attr);
+ if (id == e->callback_id)
+ {
+ 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 contain -1. */
+auto_vec<int>
+callback_get_arg_mapping (cgraph_edge *e, cgraph_edge *carrying)
+{
+ tree attr = callback_fetch_attr_by_edge (e, carrying);
+ 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));
+ /* Subtract 1 to account for 1-based indexing. If the value is unknown,
+ use constant -1 instead. */
+ idx = idx == CB_UNKNOWN_POS ? -1 : idx - 1;
+ res.safe_push (idx);
+ }
+
+ return res;
+}
+
+/* For a callback pair, returns the 0-based index of the address of
+ E's callee in the argument list of CARRYING's callee decl. */
+int
+callback_fetch_fn_position (cgraph_edge *e, cgraph_edge *carrying)
+{
+ tree attr = callback_fetch_attr_by_edge (e, 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. */
+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;
+}
+
+/* Returns the number of arguments the callback function described by ATTR
+ takes. */
+
+size_t
+callback_num_args (tree attr)
+{
+ tree args = TREE_VALUE (attr);
+ size_t res = 0;
+ tree it;
+
+ for (it = TREE_CHAIN (args); it != NULL_TREE; it = TREE_CHAIN (it), ++res)
+ ;
+ return res;
+}