Line data Source code
1 : /* Callback attribute handling
2 : Copyright (C) 2025-2026 Free Software Foundation, Inc.
3 : Contributed by Josef Melcr <jmelcr@gcc.gnu.org>
4 :
5 : This file is part of GCC.
6 :
7 : GCC is free software; you can redistribute it and/or modify
8 : under the terms of the GNU General Public License as published by
9 : the Free Software Foundation; either version 3 of the License, or
10 : (at your option) any later version.
11 :
12 : GCC is distributed in the hope that it will be useful,
13 : but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : GNU General Public License for more details.
16 :
17 : You should have received a copy of the GNU General Public License
18 : along with GCC; see the file COPYING3. If not see
19 : <http://www.gnu.org/licenses/>. */
20 :
21 : #include "config.h"
22 : #include "system.h"
23 : #include "coretypes.h"
24 : #include "backend.h"
25 : #include "tree.h"
26 : #include "gimple.h"
27 : #include "alloc-pool.h"
28 : #include "cgraph.h"
29 : #include "diagnostic.h"
30 : #include "builtins.h"
31 : #include "options.h"
32 : #include "gimple-range.h"
33 : #include "attribs.h"
34 : #include "attr-callback.h"
35 :
36 : /* Returns a callback attribute with callback index FN_IDX, and ARG_COUNT
37 : arguments specified by VA_ARGS. */
38 : tree
39 379772 : callback_build_attr (unsigned fn_idx, unsigned arg_count...)
40 : {
41 379772 : va_list args;
42 379772 : va_start (args, arg_count);
43 :
44 379772 : tree cblist = NULL_TREE;
45 379772 : tree *pp = &cblist;
46 379772 : unsigned i;
47 759544 : for (i = 0; i < arg_count; i++)
48 : {
49 379772 : int num = va_arg (args, int);
50 379772 : tree tnum = build_int_cst (integer_type_node, num);
51 379772 : *pp = build_tree_list (NULL, tnum);
52 379772 : pp = &TREE_CHAIN (*pp);
53 : }
54 379772 : cblist
55 379772 : = tree_cons (NULL_TREE, build_int_cst (integer_type_node, fn_idx), cblist);
56 379772 : tree attr
57 379772 : = tree_cons (get_identifier (CALLBACK_ATTR_IDENT), cblist, NULL_TREE);
58 379772 : return attr;
59 : }
60 :
61 : /* Returns TRUE if a function should be treated as if it had a callback
62 : attribute despite the DECL not having it. STMT can be passed NULL
63 : if the call statement is not available at the time, for example WPA, but it
64 : should be called with the statement itself whenever possible. */
65 : bool
66 2716078 : callback_is_special_cased (tree decl, gcall *stmt)
67 : {
68 2716078 : if (fndecl_built_in_p (decl, BUILT_IN_GOMP_TASK))
69 : {
70 19098 : if (stmt)
71 19098 : return gimple_call_arg (stmt, 2) == null_pointer_node;
72 : return true;
73 : }
74 : return false;
75 : }
76 :
77 : /* Returns an attribute for a special cased function. */
78 : tree
79 4304 : callback_special_case_attr (tree decl)
80 : {
81 4304 : if (fndecl_built_in_p (decl, BUILT_IN_GOMP_TASK))
82 4304 : return callback_build_attr (1, 1, 2);
83 0 : gcc_unreachable ();
84 : }
85 :
86 : /* Returns TRUE if the callee of E has a callback attribute. */
87 : bool
88 2580419 : callback_edge_callee_has_attr (cgraph_edge *e)
89 : {
90 2580419 : return lookup_attribute (CALLBACK_ATTR_IDENT,
91 2580419 : DECL_ATTRIBUTES (e->callee->decl))
92 2580419 : || callback_is_special_cased (e->callee->decl, e->call_stmt);
93 : }
94 :
95 : /* Given an instance of callback attribute, return the 0-based
96 : index of the called function in question. */
97 : int
98 34452 : callback_get_fn_index (tree cb_attr)
99 : {
100 34452 : tree args = TREE_VALUE (cb_attr);
101 34452 : int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
102 34452 : return idx;
103 : }
104 :
105 : /* For a given callback pair, retrieves the callback attribute used
106 : to create E from the callee of CARRYING. */
107 : tree
108 18494 : callback_fetch_attr_by_edge (cgraph_edge *e, cgraph_edge *carrying)
109 : {
110 18494 : gcc_checking_assert (e->call_stmt == carrying->call_stmt
111 : && e->lto_stmt_uid == carrying->lto_stmt_uid);
112 :
113 18494 : if (callback_is_special_cased (carrying->callee->decl, e->call_stmt))
114 2536 : return callback_special_case_attr (carrying->callee->decl);
115 :
116 15958 : tree cb_attr = lookup_attribute (CALLBACK_ATTR_IDENT,
117 15958 : DECL_ATTRIBUTES (carrying->callee->decl));
118 15958 : gcc_checking_assert (cb_attr);
119 15958 : tree res = NULL_TREE;
120 15958 : for (; cb_attr;
121 0 : cb_attr = lookup_attribute (CALLBACK_ATTR_IDENT, TREE_CHAIN (cb_attr)))
122 : {
123 15958 : unsigned id = callback_get_fn_index (cb_attr);
124 15958 : if (id == e->callback_id)
125 : {
126 : res = cb_attr;
127 : break;
128 : }
129 : }
130 15958 : gcc_checking_assert (res != NULL_TREE);
131 : return res;
132 : }
133 :
134 : /* Given an instance of callback attribute, return the 0-base indices
135 : of arguments passed to the callback. For a callback function taking
136 : n parameters, returns a vector of n indices of their values in the parameter
137 : list of it's caller. Indices with unknown positions contain -1. */
138 : auto_vec<int>
139 15069 : callback_get_arg_mapping (cgraph_edge *e, cgraph_edge *carrying)
140 : {
141 15069 : tree attr = callback_fetch_attr_by_edge (e, carrying);
142 15069 : gcc_checking_assert (attr);
143 15069 : tree args = TREE_VALUE (attr);
144 15069 : auto_vec<int> res;
145 15069 : tree it;
146 :
147 : /* Skip over the first argument, which denotes
148 : which argument is the called function. */
149 30138 : for (it = TREE_CHAIN (args); it != NULL_TREE; it = TREE_CHAIN (it))
150 : {
151 15069 : int idx = TREE_INT_CST_LOW (TREE_VALUE (it));
152 : /* Subtract 1 to account for 1-based indexing. If the value is unknown,
153 : use constant -1 instead. */
154 15069 : idx = idx == CB_UNKNOWN_POS ? -1 : idx - 1;
155 15069 : res.safe_push (idx);
156 : }
157 :
158 15069 : return res;
159 : }
160 :
161 : /* For a callback pair, returns the 0-based index of the address of
162 : E's callee in the argument list of CARRYING's callee decl. */
163 : int
164 3425 : callback_fetch_fn_position (cgraph_edge *e, cgraph_edge *carrying)
165 : {
166 3425 : tree attr = callback_fetch_attr_by_edge (e, carrying);
167 3425 : return callback_get_fn_index (attr);
168 : }
169 :
170 : /* Returns the element at index idx in the list or NULL_TREE if
171 : the list isn't long enough. NULL_TREE is used as the endpoint. */
172 : static tree
173 195600 : get_nth_list_elem (tree list, unsigned idx)
174 : {
175 195600 : tree res = NULL_TREE;
176 195600 : unsigned i = 0;
177 195600 : tree it;
178 293400 : for (it = list; it != NULL_TREE; it = TREE_CHAIN (it), i++)
179 : {
180 293400 : if (i == idx)
181 : {
182 195600 : res = TREE_VALUE (it);
183 195600 : break;
184 : }
185 : }
186 195600 : return res;
187 : }
188 :
189 : /* Handle a "callback" attribute; arguments as in
190 : struct attribute_spec.handler. */
191 : tree
192 97800 : handle_callback_attribute (tree *node, tree name, tree args,
193 : int ARG_UNUSED (flags), bool *no_add_attrs)
194 : {
195 97800 : tree decl = *node;
196 97800 : if (TREE_CODE (decl) != FUNCTION_DECL)
197 : {
198 0 : error_at (DECL_SOURCE_LOCATION (decl),
199 : "%qE attribute can only be used on functions", name);
200 0 : *no_add_attrs = true;
201 : }
202 :
203 97800 : tree cb_fn_idx_node = TREE_VALUE (args);
204 97800 : if (TREE_CODE (cb_fn_idx_node) != INTEGER_CST)
205 : {
206 0 : error_at (DECL_SOURCE_LOCATION (decl),
207 : "argument specifying callback function position is not an "
208 : "integer constant");
209 0 : *no_add_attrs = true;
210 0 : return NULL_TREE;
211 : }
212 : /* We have to use the function type for validation, as
213 : DECL_ARGUMENTS returns NULL at this point. */
214 97800 : int callback_fn_idx = TREE_INT_CST_LOW (cb_fn_idx_node);
215 97800 : tree decl_type_args = TYPE_ARG_TYPES (TREE_TYPE (decl));
216 97800 : tree it;
217 97800 : int decl_nargs = list_length (decl_type_args);
218 839450 : for (it = decl_type_args; it != NULL_TREE; it = TREE_CHAIN (it))
219 741650 : if (it == void_list_node)
220 : {
221 97800 : --decl_nargs;
222 97800 : break;
223 : }
224 97800 : if (callback_fn_idx == CB_UNKNOWN_POS)
225 : {
226 0 : error_at (DECL_SOURCE_LOCATION (decl),
227 : "callback function position cannot be marked as unknown");
228 0 : *no_add_attrs = true;
229 0 : return NULL_TREE;
230 : }
231 97800 : --callback_fn_idx;
232 97800 : if (callback_fn_idx >= decl_nargs)
233 : {
234 0 : error_at (DECL_SOURCE_LOCATION (decl),
235 : "callback function position out of range");
236 0 : *no_add_attrs = true;
237 0 : return NULL_TREE;
238 : }
239 :
240 : /* Search for the type of the callback function
241 : in parameters of the original function. */
242 97800 : tree cfn = get_nth_list_elem (decl_type_args, callback_fn_idx);
243 97800 : if (cfn == NULL_TREE)
244 : {
245 0 : error_at (DECL_SOURCE_LOCATION (decl),
246 : "could not retrieve callback function from arguments");
247 0 : *no_add_attrs = true;
248 0 : return NULL_TREE;
249 : }
250 97800 : tree cfn_pointee_type = TREE_TYPE (cfn);
251 97800 : if (TREE_CODE (cfn) != POINTER_TYPE
252 97800 : || TREE_CODE (cfn_pointee_type) != FUNCTION_TYPE)
253 : {
254 0 : error_at (DECL_SOURCE_LOCATION (decl),
255 : "argument no. %d is not an address of a function",
256 : callback_fn_idx + 1);
257 0 : *no_add_attrs = true;
258 0 : return NULL_TREE;
259 : }
260 :
261 97800 : tree type_args = TYPE_ARG_TYPES (cfn_pointee_type);
262 : /* Compare the length of the list of argument indices
263 : and the real number of parameters the callback takes. */
264 97800 : unsigned cfn_nargs = list_length (TREE_CHAIN (args));
265 97800 : unsigned type_nargs = list_length (type_args);
266 195600 : for (it = type_args; it != NULL_TREE; it = TREE_CHAIN (it))
267 195600 : if (it == void_list_node)
268 : {
269 97800 : --type_nargs;
270 97800 : break;
271 : }
272 97800 : if (cfn_nargs != type_nargs)
273 : {
274 0 : error_at (DECL_SOURCE_LOCATION (decl),
275 : "argument number mismatch, %d expected, got %d", type_nargs,
276 : cfn_nargs);
277 0 : *no_add_attrs = true;
278 0 : return NULL_TREE;
279 : }
280 :
281 97800 : unsigned curr = 0;
282 97800 : tree cfn_it;
283 : /* Validate type compatibility of the arguments passed
284 : from caller function to callback. "it" is used to step
285 : through the parameters of the caller, "cfn_it" is
286 : stepping through the parameters of the callback. */
287 195600 : for (it = type_args, cfn_it = TREE_CHAIN (args); curr < type_nargs;
288 97800 : it = TREE_CHAIN (it), cfn_it = TREE_CHAIN (cfn_it), curr++)
289 : {
290 97800 : if (TREE_CODE (TREE_VALUE (cfn_it)) != INTEGER_CST)
291 : {
292 0 : error_at (DECL_SOURCE_LOCATION (decl),
293 : "argument no. %d is not an integer constant", curr + 1);
294 0 : *no_add_attrs = true;
295 0 : continue;
296 : }
297 :
298 97800 : int arg_idx = TREE_INT_CST_LOW (TREE_VALUE (cfn_it));
299 :
300 : /* No need to check for type compatibility,
301 : if we don't know what we are passing. */
302 97800 : if (arg_idx == CB_UNKNOWN_POS)
303 0 : continue;
304 :
305 97800 : arg_idx -= 1;
306 : /* Report an error if the position is out of bounds,
307 : but we can still check the rest of the arguments. */
308 97800 : if (arg_idx >= decl_nargs)
309 : {
310 0 : error_at (DECL_SOURCE_LOCATION (decl),
311 : "callback argument index %d is out of range", arg_idx + 1);
312 0 : *no_add_attrs = true;
313 0 : continue;
314 : }
315 :
316 97800 : tree arg_type = get_nth_list_elem (decl_type_args, arg_idx);
317 97800 : tree expected_type = TREE_VALUE (it);
318 : /* Check the type of the value we are about to pass ("arg_type")
319 : for compatibility with the actual type the callback function
320 : expects ("expected_type"). */
321 97800 : if (!types_compatible_p (expected_type, arg_type))
322 : {
323 0 : error_at (DECL_SOURCE_LOCATION (decl),
324 : "argument type at index %d is not compatible with callback "
325 : "argument type at index %d",
326 : arg_idx + 1, curr + 1);
327 0 : *no_add_attrs = true;
328 0 : continue;
329 : }
330 : }
331 :
332 : /* Check that the decl does not already have a callback attribute describing
333 : the same argument. */
334 97800 : it = lookup_attribute (CALLBACK_ATTR_IDENT, DECL_ATTRIBUTES (decl));
335 195600 : for (; it; it = lookup_attribute (CALLBACK_ATTR_IDENT, TREE_CHAIN (it)))
336 0 : if (callback_get_fn_index (it) == callback_fn_idx)
337 : {
338 0 : error_at (DECL_SOURCE_LOCATION (decl),
339 : "function declaration has multiple callback attributes "
340 : "describing argument no. %d",
341 : callback_fn_idx + 1);
342 0 : *no_add_attrs = true;
343 0 : break;
344 : }
345 :
346 : return NULL_TREE;
347 : }
348 :
349 : /* Returns TRUE if E is considered useful in the callgraph, FALSE otherwise. If
350 : this predicate returns FALSE, then E wasn't used to optimize its callee and
351 : can be safely removed from the callgraph. */
352 : bool
353 13675 : callback_edge_useful_p (cgraph_edge *e)
354 : {
355 13675 : gcc_checking_assert (e->callback);
356 : /* If the edge is pointing towards a clone, it is useful. */
357 13675 : if (e->callee->clone_of)
358 : return true;
359 :
360 : /* If the callee has been produced by icf, the edge is useful, as it will be
361 : used to for the redirection. */
362 13576 : if (e->callee->icf_merged)
363 273 : return true;
364 :
365 : /* In case some future pass redirects edges, it should be added as a case
366 : here. */
367 :
368 : return false;
369 : }
370 :
371 : /* Returns the number of arguments the callback function described by ATTR
372 : takes. */
373 :
374 : size_t
375 15069 : callback_num_args (tree attr)
376 : {
377 15069 : tree args = TREE_VALUE (attr);
378 15069 : size_t res = 0;
379 15069 : tree it;
380 :
381 30138 : for (it = TREE_CHAIN (args); it != NULL_TREE; it = TREE_CHAIN (it), ++res)
382 : ;
383 15069 : return res;
384 : }
|