1 | /* Localize comdats. |
2 | Copyright (C) 2014-2023 Free Software Foundation, Inc. |
3 | |
4 | This file is part of GCC. |
5 | |
6 | GCC is free software; you can redistribute it and/or modify it under |
7 | the terms of the GNU General Public License as published by the Free |
8 | Software Foundation; either version 3, or (at your option) any later |
9 | version. |
10 | |
11 | GCC is distributed in the hope that it will be useful, but WITHOUT ANY |
12 | WARRANTY; without even the implied warranty of MERCHANTABILITY or |
13 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
14 | for more details. |
15 | |
16 | You should have received a copy of the GNU General Public License |
17 | along with GCC; see the file COPYING3. If not see |
18 | <http://www.gnu.org/licenses/>. */ |
19 | |
20 | /* This is very simple pass that looks for static symbols that are used |
21 | exclusively by symbol within one comdat group. In this case it makes |
22 | sense to bring the symbol itself into the group to avoid dead code |
23 | that would arrise when the comdat group from current unit is replaced |
24 | by a different copy. Consider for example: |
25 | |
26 | static int q(void) |
27 | { |
28 | .... |
29 | } |
30 | inline int t(void) |
31 | { |
32 | return q(); |
33 | } |
34 | |
35 | if Q is used only by T, it makes sense to put Q into T's comdat group. |
36 | |
37 | The pass solve simple dataflow across the callgraph trying to prove what |
38 | symbols are used exclusively from a given comdat group. |
39 | |
40 | The implementation maintains a queue linked by AUX pointer terminated by |
41 | pointer value 1. Lattice values are NULL for TOP, actual comdat group, or |
42 | ERROR_MARK_NODE for bottom. |
43 | |
44 | TODO: When symbol is used only by comdat symbols, but from different groups, |
45 | it would make sense to produce a new comdat group for it with anonymous name. |
46 | |
47 | TODO2: We can't mix variables and functions within one group. Currently |
48 | we just give up on references of symbols of different types. We also should |
49 | handle this by anonymous comdat group section. */ |
50 | |
51 | #include "config.h" |
52 | #include "system.h" |
53 | #include "coretypes.h" |
54 | #include "tm.h" |
55 | #include "tree.h" |
56 | #include "tree-pass.h" |
57 | #include "cgraph.h" |
58 | |
59 | /* Main dataflow loop propagating comdat groups across |
60 | the symbol table. All references to SYMBOL are examined |
61 | and NEWGROUP is updated accordingly. MAP holds current lattice |
62 | values for individual symbols. */ |
63 | |
64 | tree |
65 | propagate_comdat_group (struct symtab_node *symbol, |
66 | tree newgroup, hash_map<symtab_node *, tree> &map) |
67 | { |
68 | int i; |
69 | struct ipa_ref *ref; |
70 | |
71 | /* Walk all references to SYMBOL, recursively dive into aliases. */ |
72 | |
73 | for (i = 0; |
74 | symbol->iterate_referring (i, ref) |
75 | && newgroup != error_mark_node; i++) |
76 | { |
77 | struct symtab_node *symbol2 = ref->referring; |
78 | |
79 | if (ref->use == IPA_REF_ALIAS) |
80 | { |
81 | newgroup = propagate_comdat_group (symbol: symbol2, newgroup, map); |
82 | continue; |
83 | } |
84 | |
85 | /* One COMDAT group cannot hold both variables and functions at |
86 | a same time. For now we just go to BOTTOM, in future we may |
87 | invent special comdat groups for this case. */ |
88 | |
89 | if (symbol->type != symbol2->type) |
90 | { |
91 | newgroup = error_mark_node; |
92 | break; |
93 | } |
94 | |
95 | /* If we see inline clone, its comdat group actually |
96 | corresponds to the comdat group of the function it is inlined |
97 | to. */ |
98 | |
99 | if (cgraph_node * cn = dyn_cast <cgraph_node *> (p: symbol2)) |
100 | { |
101 | if (cn->inlined_to) |
102 | symbol2 = cn->inlined_to; |
103 | } |
104 | |
105 | /* The actual merge operation. */ |
106 | |
107 | tree *val2 = map.get (k: symbol2); |
108 | |
109 | if (val2 && *val2 != newgroup) |
110 | { |
111 | if (!newgroup) |
112 | newgroup = *val2; |
113 | else |
114 | newgroup = error_mark_node; |
115 | } |
116 | } |
117 | |
118 | /* If we analyze function, walk also callers. */ |
119 | |
120 | cgraph_node *cnode = dyn_cast <cgraph_node *> (p: symbol); |
121 | |
122 | if (cnode) |
123 | for (struct cgraph_edge * edge = cnode->callers; |
124 | edge && newgroup != error_mark_node; edge = edge->next_caller) |
125 | { |
126 | struct symtab_node *symbol2 = edge->caller; |
127 | |
128 | if (cgraph_node * cn = dyn_cast <cgraph_node *> (p: symbol2)) |
129 | { |
130 | /* Thunks cannot call across section boundary. */ |
131 | if (cn->thunk) |
132 | newgroup = propagate_comdat_group (symbol: symbol2, newgroup, map); |
133 | /* If we see inline clone, its comdat group actually |
134 | corresponds to the comdat group of the function it |
135 | is inlined to. */ |
136 | if (cn->inlined_to) |
137 | symbol2 = cn->inlined_to; |
138 | } |
139 | |
140 | /* The actual merge operation. */ |
141 | |
142 | tree *val2 = map.get (k: symbol2); |
143 | |
144 | if (val2 && *val2 != newgroup) |
145 | { |
146 | if (!newgroup) |
147 | newgroup = *val2; |
148 | else |
149 | newgroup = error_mark_node; |
150 | } |
151 | } |
152 | return newgroup; |
153 | } |
154 | |
155 | |
156 | /* Add all references of SYMBOL that are defined into queue started by FIRST |
157 | and linked by AUX pointer (unless they are already enqueued). |
158 | Walk recursively inlined functions. */ |
159 | |
160 | void |
161 | enqueue_references (symtab_node **first, |
162 | symtab_node *symbol) |
163 | { |
164 | int i; |
165 | struct ipa_ref *ref = NULL; |
166 | |
167 | for (i = 0; symbol->iterate_reference (i, ref); i++) |
168 | { |
169 | symtab_node *node = ref->referred->ultimate_alias_target (); |
170 | |
171 | /* Always keep thunks in same sections as target function. */ |
172 | if (is_a <cgraph_node *>(p: node)) |
173 | node = dyn_cast <cgraph_node *> (p: node)->function_symbol (); |
174 | if (!node->aux && node->definition) |
175 | { |
176 | node->aux = *first; |
177 | *first = node; |
178 | } |
179 | } |
180 | |
181 | if (cgraph_node *cnode = dyn_cast <cgraph_node *> (p: symbol)) |
182 | { |
183 | struct cgraph_edge *edge; |
184 | |
185 | for (edge = cnode->callees; edge; edge = edge->next_callee) |
186 | if (!edge->inline_failed) |
187 | enqueue_references (first, symbol: edge->callee); |
188 | else |
189 | { |
190 | symtab_node *node = edge->callee->ultimate_alias_target (); |
191 | |
192 | /* Always keep thunks in same sections as target function. */ |
193 | if (is_a <cgraph_node *>(p: node)) |
194 | node = dyn_cast <cgraph_node *> (p: node)->function_symbol (); |
195 | if (!node->aux && node->definition) |
196 | { |
197 | node->aux = *first; |
198 | *first = node; |
199 | } |
200 | } |
201 | } |
202 | } |
203 | |
204 | /* Set comdat group of SYMBOL to GROUP. |
205 | Callback for for_node_and_aliases. */ |
206 | |
207 | bool |
208 | set_comdat_group (symtab_node *symbol, |
209 | void *head_p) |
210 | { |
211 | symtab_node *head = (symtab_node *)head_p; |
212 | |
213 | gcc_assert (!symbol->get_comdat_group ()); |
214 | if (symbol->real_symbol_p ()) |
215 | { |
216 | symbol->set_comdat_group (head->get_comdat_group ()); |
217 | symbol->add_to_same_comdat_group (old_node: head); |
218 | } |
219 | return false; |
220 | } |
221 | |
222 | /* Set comdat group of SYMBOL to GROUP. |
223 | Callback for for_node_thunks_and_aliases. */ |
224 | |
225 | bool |
226 | set_comdat_group_1 (cgraph_node *symbol, |
227 | void *head_p) |
228 | { |
229 | return set_comdat_group (symbol, head_p); |
230 | } |
231 | |
232 | /* The actual pass with the main dataflow loop. */ |
233 | |
234 | static unsigned int |
235 | ipa_comdats (void) |
236 | { |
237 | hash_map<symtab_node *, tree> map (251); |
238 | hash_map<tree, symtab_node *> comdat_head_map (251); |
239 | symtab_node *symbol; |
240 | bool comdat_group_seen = false; |
241 | symtab_node *first = (symtab_node *) (void *) 1; |
242 | tree group; |
243 | |
244 | /* Start the dataflow by assigning comdat group to symbols that are in comdat |
245 | groups already. All other externally visible symbols must stay, we use |
246 | ERROR_MARK_NODE as bottom for the propagation. */ |
247 | |
248 | FOR_EACH_DEFINED_SYMBOL (symbol) |
249 | if (!symbol->real_symbol_p ()) |
250 | ; |
251 | else if ((group = symbol->get_comdat_group ()) != NULL) |
252 | { |
253 | map.put (k: symbol, v: group); |
254 | comdat_head_map.put (k: group, v: symbol); |
255 | comdat_group_seen = true; |
256 | |
257 | /* Mark the symbol so we won't waste time visiting it for dataflow. */ |
258 | symbol->aux = (symtab_node *) (void *) 1; |
259 | } |
260 | /* See symbols that cannot be privatized to comdats; that is externally |
261 | visible symbols or otherwise used ones. We also do not want to mangle |
262 | user section names. */ |
263 | else if (symbol->externally_visible |
264 | || symbol->force_output |
265 | || symbol->used_from_other_partition |
266 | || TREE_THIS_VOLATILE (symbol->decl) |
267 | || symbol->get_section () |
268 | || (TREE_CODE (symbol->decl) == FUNCTION_DECL |
269 | && (DECL_STATIC_CONSTRUCTOR (symbol->decl) |
270 | || DECL_STATIC_DESTRUCTOR (symbol->decl)))) |
271 | { |
272 | symtab_node *target = symbol->ultimate_alias_target (); |
273 | |
274 | /* Always keep thunks in same sections as target function. */ |
275 | if (is_a <cgraph_node *>(p: target)) |
276 | target = dyn_cast <cgraph_node *> (p: target)->function_symbol (); |
277 | map.put (k: target, error_mark_node); |
278 | |
279 | /* Mark the symbol so we won't waste time visiting it for dataflow. */ |
280 | symbol->aux = (symtab_node *) (void *) 1; |
281 | } |
282 | else |
283 | { |
284 | /* Enqueue symbol for dataflow. */ |
285 | symbol->aux = first; |
286 | first = symbol; |
287 | } |
288 | |
289 | if (!comdat_group_seen) |
290 | { |
291 | FOR_EACH_DEFINED_SYMBOL (symbol) |
292 | symbol->aux = NULL; |
293 | return 0; |
294 | } |
295 | |
296 | /* The actual dataflow. */ |
297 | |
298 | while (first != (void *) 1) |
299 | { |
300 | tree group = NULL; |
301 | tree newgroup, *val; |
302 | |
303 | symbol = first; |
304 | first = (symtab_node *)first->aux; |
305 | |
306 | /* Get current lattice value of SYMBOL. */ |
307 | val = map.get (k: symbol); |
308 | if (val) |
309 | group = *val; |
310 | |
311 | /* If it is bottom, there is nothing to do; do not clear AUX |
312 | so we won't re-queue the symbol. */ |
313 | if (group == error_mark_node) |
314 | continue; |
315 | |
316 | newgroup = propagate_comdat_group (symbol, newgroup: group, map); |
317 | |
318 | /* If nothing changed, proceed to next symbol. */ |
319 | if (newgroup == group) |
320 | { |
321 | symbol->aux = NULL; |
322 | continue; |
323 | } |
324 | |
325 | /* Update lattice value and enqueue all references for re-visiting. */ |
326 | gcc_assert (newgroup); |
327 | if (val) |
328 | *val = newgroup; |
329 | else |
330 | map.put (k: symbol, v: newgroup); |
331 | enqueue_references (first: &first, symbol); |
332 | |
333 | /* We may need to revisit the symbol unless it is BOTTOM. */ |
334 | if (newgroup != error_mark_node) |
335 | symbol->aux = NULL; |
336 | } |
337 | |
338 | /* Finally assign symbols to the sections. */ |
339 | |
340 | FOR_EACH_DEFINED_SYMBOL (symbol) |
341 | { |
342 | struct cgraph_node *fun; |
343 | symbol->aux = NULL; |
344 | if (!symbol->get_comdat_group () |
345 | && !symbol->alias |
346 | && (!(fun = dyn_cast <cgraph_node *> (p: symbol)) |
347 | || !fun->thunk) |
348 | && symbol->real_symbol_p ()) |
349 | { |
350 | tree *val = map.get (k: symbol); |
351 | |
352 | /* A NULL here means that SYMBOL is unreachable in the definition |
353 | of ipa-comdats. Either ipa-comdats is wrong about this or someone |
354 | forgot to cleanup and remove unreachable functions earlier. */ |
355 | gcc_assert (val); |
356 | |
357 | tree group = *val; |
358 | |
359 | if (group == error_mark_node) |
360 | continue; |
361 | if (dump_file) |
362 | { |
363 | fprintf (stream: dump_file, format: "Localizing symbol\n" ); |
364 | symbol->dump (f: dump_file); |
365 | fprintf (stream: dump_file, format: "To group: %s\n" , IDENTIFIER_POINTER (group)); |
366 | } |
367 | if (is_a <cgraph_node *> (p: symbol)) |
368 | dyn_cast <cgraph_node *>(p: symbol)->call_for_symbol_thunks_and_aliases |
369 | (callback: set_comdat_group_1, |
370 | data: *comdat_head_map.get (k: group), |
371 | include_overwritable: true); |
372 | else |
373 | symbol->call_for_symbol_and_aliases |
374 | (callback: set_comdat_group, |
375 | data: *comdat_head_map.get (k: group), |
376 | include_overwritable: true); |
377 | } |
378 | } |
379 | |
380 | #if 0 |
381 | /* Recompute calls comdat local flag. This need to be done after all changes |
382 | are made. */ |
383 | cgraph_node *function; |
384 | FOR_EACH_DEFINED_FUNCTION (function) |
385 | if (function->get_comdat_group ()) |
386 | function->calls_comdat_local = function->check_calls_comdat_local_p (); |
387 | #endif |
388 | return 0; |
389 | } |
390 | |
391 | namespace { |
392 | |
393 | const pass_data pass_data_ipa_comdats = |
394 | { |
395 | .type: IPA_PASS, /* type */ |
396 | .name: "comdats" , /* name */ |
397 | .optinfo_flags: OPTGROUP_NONE, /* optinfo_flags */ |
398 | .tv_id: TV_IPA_COMDATS, /* tv_id */ |
399 | .properties_required: 0, /* properties_required */ |
400 | .properties_provided: 0, /* properties_provided */ |
401 | .properties_destroyed: 0, /* properties_destroyed */ |
402 | .todo_flags_start: 0, /* todo_flags_start */ |
403 | .todo_flags_finish: 0, /* todo_flags_finish */ |
404 | }; |
405 | |
406 | class pass_ipa_comdats : public ipa_opt_pass_d |
407 | { |
408 | public: |
409 | pass_ipa_comdats (gcc::context *ctxt) |
410 | : ipa_opt_pass_d (pass_data_ipa_comdats, ctxt, |
411 | NULL, /* generate_summary */ |
412 | NULL, /* write_summary */ |
413 | NULL, /* read_summary */ |
414 | NULL, /* write_optimization_summary */ |
415 | NULL, /* read_optimization_summary */ |
416 | NULL, /* stmt_fixup */ |
417 | 0, /* function_transform_todo_flags_start */ |
418 | NULL, /* function_transform */ |
419 | NULL) /* variable_transform */ |
420 | {} |
421 | |
422 | /* opt_pass methods: */ |
423 | bool gate (function *) final override; |
424 | unsigned int execute (function *) final override { return ipa_comdats (); } |
425 | |
426 | }; // class pass_ipa_comdats |
427 | |
428 | bool |
429 | pass_ipa_comdats::gate (function *) |
430 | { |
431 | return HAVE_COMDAT_GROUP; |
432 | } |
433 | |
434 | } // anon namespace |
435 | |
436 | ipa_opt_pass_d * |
437 | make_pass_ipa_comdats (gcc::context *ctxt) |
438 | { |
439 | return new pass_ipa_comdats (ctxt); |
440 | } |
441 | |