mirror of
https://forge.sourceware.org/marek/gcc.git
synced 2026-02-21 19:35:36 -05:00
385 lines
12 KiB
C++
385 lines
12 KiB
C++
/* Callback attribute handling
|
|
Copyright (C) 2025-2026 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);
|
|
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 ();
|
|
}
|
|
|
|
/* Returns TRUE if the callee of E has a callback attribute. */
|
|
bool
|
|
callback_edge_callee_has_attr (cgraph_edge *e)
|
|
{
|
|
return lookup_attribute (CALLBACK_ATTR_IDENT,
|
|
DECL_ATTRIBUTES (e->callee->decl))
|
|
|| callback_is_special_cased (e->callee->decl, e->call_stmt);
|
|
}
|
|
|
|
/* 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 pointing towards a clone, it is useful. */
|
|
if (e->callee->clone_of)
|
|
return true;
|
|
|
|
/* If the callee has been produced by icf, the edge is useful, as it will be
|
|
used to for the redirection. */
|
|
if (e->callee->icf_merged)
|
|
return true;
|
|
|
|
/* In case some future pass redirects edges, it should be added as a case
|
|
here. */
|
|
|
|
return false;
|
|
}
|
|
|
|
/* 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;
|
|
}
|