| /* Localize comdats. | |
| Copyright (C) 2014-2026 Free Software Foundation, Inc. | |
| This file is part of GCC. | |
| GCC is free software; you can redistribute it and/or modify it under | |
| the terms of the GNU General Public License as published by the Free | |
| Software Foundation; either version 3, 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/>. */ | |
| /* This is very simple pass that looks for static symbols that are used | |
| exclusively by symbol within one comdat group. In this case it makes | |
| sense to bring the symbol itself into the group to avoid dead code | |
| that would arise when the comdat group from current unit is replaced | |
| by a different copy. Consider for example: | |
| static int q(void) | |
| { | |
| .... | |
| } | |
| inline int t(void) | |
| { | |
| return q(); | |
| } | |
| if Q is used only by T, it makes sense to put Q into T's comdat group. | |
| The pass solve simple dataflow across the callgraph trying to prove what | |
| symbols are used exclusively from a given comdat group. | |
| The implementation maintains a queue linked by AUX pointer terminated by | |
| pointer value 1. Lattice values are NULL for TOP, actual comdat group, or | |
| ERROR_MARK_NODE for bottom. | |
| TODO: When symbol is used only by comdat symbols, but from different groups, | |
| it would make sense to produce a new comdat group for it with anonymous name. | |
| TODO2: We can't mix variables and functions within one group. Currently | |
| we just give up on references of symbols of different types. We also should | |
| handle this by anonymous comdat group section. */ | |
| #include "config.h" | |
| #include "system.h" | |
| #include "coretypes.h" | |
| #include "tm.h" | |
| #include "tree.h" | |
| #include "tree-pass.h" | |
| #include "cgraph.h" | |
| /* Main dataflow loop propagating comdat groups across | |
| the symbol table. All references to SYMBOL are examined | |
| and NEWGROUP is updated accordingly. MAP holds current lattice | |
| values for individual symbols. */ | |
| tree | |
| propagate_comdat_group (struct symtab_node *symbol, | |
| tree newgroup, hash_map<symtab_node *, tree> &map) | |
| { | |
| int i; | |
| struct ipa_ref *ref; | |
| /* Walk all references to SYMBOL, recursively dive into aliases. */ | |
| for (i = 0; | |
| symbol->iterate_referring (i, ref) | |
| && newgroup != error_mark_node; i++) | |
| { | |
| struct symtab_node *symbol2 = ref->referring; | |
| if (ref->use == IPA_REF_ALIAS) | |
| { | |
| newgroup = propagate_comdat_group (symbol2, newgroup, map); | |
| continue; | |
| } | |
| /* One COMDAT group cannot hold both variables and functions at | |
| a same time. For now we just go to BOTTOM, in future we may | |
| invent special comdat groups for this case. */ | |
| if (symbol->type != symbol2->type) | |
| { | |
| newgroup = error_mark_node; | |
| break; | |
| } | |
| /* If we see inline clone, its comdat group actually | |
| corresponds to the comdat group of the function it is inlined | |
| to. */ | |
| if (cgraph_node * cn = dyn_cast <cgraph_node *> (symbol2)) | |
| { | |
| if (cn->inlined_to) | |
| symbol2 = cn->inlined_to; | |
| } | |
| /* The actual merge operation. */ | |
| tree *val2 = map.get (symbol2); | |
| if (val2 && *val2 != newgroup) | |
| { | |
| if (!newgroup) | |
| newgroup = *val2; | |
| else | |
| newgroup = error_mark_node; | |
| } | |
| } | |
| /* If we analyze function, walk also callers. */ | |
| cgraph_node *cnode = dyn_cast <cgraph_node *> (symbol); | |
| if (cnode) | |
| for (struct cgraph_edge * edge = cnode->callers; | |
| edge && newgroup != error_mark_node; edge = edge->next_caller) | |
| { | |
| struct symtab_node *symbol2 = edge->caller; | |
| if (cgraph_node * cn = dyn_cast <cgraph_node *> (symbol2)) | |
| { | |
| /* Thunks cannot call across section boundary. */ | |
| if (cn->thunk) | |
| newgroup = propagate_comdat_group (symbol2, newgroup, map); | |
| /* If we see inline clone, its comdat group actually | |
| corresponds to the comdat group of the function it | |
| is inlined to. */ | |
| if (cn->inlined_to) | |
| symbol2 = cn->inlined_to; | |
| } | |
| /* The actual merge operation. */ | |
| tree *val2 = map.get (symbol2); | |
| if (val2 && *val2 != newgroup) | |
| { | |
| if (!newgroup) | |
| newgroup = *val2; | |
| else | |
| newgroup = error_mark_node; | |
| } | |
| } | |
| return newgroup; | |
| } | |
| /* Add all references of SYMBOL that are defined into queue started by FIRST | |
| and linked by AUX pointer (unless they are already enqueued). | |
| Walk recursively inlined functions. */ | |
| void | |
| enqueue_references (symtab_node **first, | |
| symtab_node *symbol) | |
| { | |
| int i; | |
| struct ipa_ref *ref = NULL; | |
| for (i = 0; symbol->iterate_reference (i, ref); i++) | |
| { | |
| symtab_node *node = ref->referred->ultimate_alias_target (); | |
| /* Always keep thunks in same sections as target function. */ | |
| if (is_a <cgraph_node *>(node)) | |
| node = dyn_cast <cgraph_node *> (node)->function_symbol (); | |
| if (!node->aux && node->definition) | |
| { | |
| node->aux = *first; | |
| *first = node; | |
| } | |
| } | |
| if (cgraph_node *cnode = dyn_cast <cgraph_node *> (symbol)) | |
| { | |
| struct cgraph_edge *edge; | |
| for (edge = cnode->callees; edge; edge = edge->next_callee) | |
| if (!edge->inline_failed) | |
| enqueue_references (first, edge->callee); | |
| else | |
| { | |
| symtab_node *node = edge->callee->ultimate_alias_target (); | |
| /* Always keep thunks in same sections as target function. */ | |
| if (is_a <cgraph_node *>(node)) | |
| node = dyn_cast <cgraph_node *> (node)->function_symbol (); | |
| if (!node->aux && node->definition) | |
| { | |
| node->aux = *first; | |
| *first = node; | |
| } | |
| } | |
| } | |
| } | |
| /* Set comdat group of SYMBOL to GROUP. | |
| Callback for for_node_and_aliases. */ | |
| bool | |
| set_comdat_group (symtab_node *symbol, | |
| void *head_p) | |
| { | |
| symtab_node *head = (symtab_node *)head_p; | |
| gcc_assert (!symbol->get_comdat_group ()); | |
| if (symbol->real_symbol_p ()) | |
| { | |
| symbol->set_comdat_group (head->get_comdat_group ()); | |
| symbol->add_to_same_comdat_group (head); | |
| } | |
| return false; | |
| } | |
| /* Set comdat group of SYMBOL to GROUP. | |
| Callback for for_node_thunks_and_aliases. */ | |
| bool | |
| set_comdat_group_1 (cgraph_node *symbol, | |
| void *head_p) | |
| { | |
| return set_comdat_group (symbol, head_p); | |
| } | |
| /* The actual pass with the main dataflow loop. */ | |
| static unsigned int | |
| ipa_comdats (void) | |
| { | |
| hash_map<symtab_node *, tree> map (251); | |
| hash_map<tree, symtab_node *> comdat_head_map (251); | |
| symtab_node *symbol; | |
| bool comdat_group_seen = false; | |
| symtab_node *first = (symtab_node *) (void *) 1; | |
| tree group; | |
| /* Start the dataflow by assigning comdat group to symbols that are in comdat | |
| groups already. All other externally visible symbols must stay, we use | |
| ERROR_MARK_NODE as bottom for the propagation. */ | |
| FOR_EACH_DEFINED_SYMBOL (symbol) | |
| if (!symbol->real_symbol_p ()) | |
| ; | |
| else if ((group = symbol->get_comdat_group ()) != NULL) | |
| { | |
| map.put (symbol, group); | |
| comdat_head_map.put (group, symbol); | |
| comdat_group_seen = true; | |
| /* Mark the symbol so we won't waste time visiting it for dataflow. */ | |
| symbol->aux = (symtab_node *) (void *) 1; | |
| } | |
| /* See symbols that cannot be privatized to comdats; that is externally | |
| visible symbols or otherwise used ones. We also do not want to mangle | |
| user section names. */ | |
| else if (symbol->externally_visible | |
| || symbol->force_output | |
| || symbol->ref_by_asm | |
| || symbol->used_from_other_partition | |
| || TREE_THIS_VOLATILE (symbol->decl) | |
| || symbol->get_section () | |
| || (TREE_CODE (symbol->decl) == FUNCTION_DECL | |
| && (DECL_STATIC_CONSTRUCTOR (symbol->decl) | |
| || DECL_STATIC_DESTRUCTOR (symbol->decl)))) | |
| { | |
| symtab_node *target = symbol->ultimate_alias_target (); | |
| /* Always keep thunks in same sections as target function. */ | |
| if (is_a <cgraph_node *>(target)) | |
| target = dyn_cast <cgraph_node *> (target)->function_symbol (); | |
| map.put (target, error_mark_node); | |
| /* Mark the symbol so we won't waste time visiting it for dataflow. */ | |
| symbol->aux = (symtab_node *) (void *) 1; | |
| } | |
| else | |
| { | |
| /* Enqueue symbol for dataflow. */ | |
| symbol->aux = first; | |
| first = symbol; | |
| } | |
| if (!comdat_group_seen) | |
| { | |
| FOR_EACH_DEFINED_SYMBOL (symbol) | |
| symbol->aux = NULL; | |
| return 0; | |
| } | |
| /* The actual dataflow. */ | |
| while (first != (void *) 1) | |
| { | |
| tree group = NULL; | |
| tree newgroup, *val; | |
| symbol = first; | |
| first = (symtab_node *)first->aux; | |
| /* Get current lattice value of SYMBOL. */ | |
| val = map.get (symbol); | |
| if (val) | |
| group = *val; | |
| /* If it is bottom, there is nothing to do; do not clear AUX | |
| so we won't re-queue the symbol. */ | |
| if (group == error_mark_node) | |
| continue; | |
| newgroup = propagate_comdat_group (symbol, group, map); | |
| /* If nothing changed, proceed to next symbol. */ | |
| if (newgroup == group) | |
| { | |
| symbol->aux = NULL; | |
| continue; | |
| } | |
| /* Update lattice value and enqueue all references for re-visiting. */ | |
| gcc_assert (newgroup); | |
| if (val) | |
| *val = newgroup; | |
| else | |
| map.put (symbol, newgroup); | |
| enqueue_references (&first, symbol); | |
| /* We may need to revisit the symbol unless it is BOTTOM. */ | |
| if (newgroup != error_mark_node) | |
| symbol->aux = NULL; | |
| } | |
| /* Finally assign symbols to the sections. */ | |
| FOR_EACH_DEFINED_SYMBOL (symbol) | |
| { | |
| struct cgraph_node *fun; | |
| symbol->aux = NULL; | |
| if (!symbol->get_comdat_group () | |
| && !symbol->alias | |
| && (!(fun = dyn_cast <cgraph_node *> (symbol)) | |
| || !fun->thunk) | |
| && symbol->real_symbol_p ()) | |
| { | |
| tree *val = map.get (symbol); | |
| /* A NULL here means that SYMBOL is unreachable in the definition | |
| of ipa-comdats. Either ipa-comdats is wrong about this or someone | |
| forgot to cleanup and remove unreachable functions earlier. */ | |
| gcc_assert (val); | |
| tree group = *val; | |
| if (group == error_mark_node) | |
| continue; | |
| if (dump_file) | |
| { | |
| fprintf (dump_file, "Localizing symbol\n"); | |
| symbol->dump (dump_file); | |
| fprintf (dump_file, "To group: %s\n", IDENTIFIER_POINTER (group)); | |
| } | |
| if (is_a <cgraph_node *> (symbol)) | |
| dyn_cast <cgraph_node *>(symbol)->call_for_symbol_thunks_and_aliases | |
| (set_comdat_group_1, | |
| *comdat_head_map.get (group), | |
| true); | |
| else | |
| symbol->call_for_symbol_and_aliases | |
| (set_comdat_group, | |
| *comdat_head_map.get (group), | |
| true); | |
| } | |
| } | |
| #if 0 | |
| /* Recompute calls comdat local flag. This need to be done after all changes | |
| are made. */ | |
| cgraph_node *function; | |
| FOR_EACH_DEFINED_FUNCTION (function) | |
| if (function->get_comdat_group ()) | |
| function->calls_comdat_local = function->check_calls_comdat_local_p (); | |
| #endif | |
| return 0; | |
| } | |
| namespace { | |
| const pass_data pass_data_ipa_comdats = | |
| { | |
| IPA_PASS, /* type */ | |
| "comdats", /* name */ | |
| OPTGROUP_NONE, /* optinfo_flags */ | |
| TV_IPA_COMDATS, /* tv_id */ | |
| 0, /* properties_required */ | |
| 0, /* properties_provided */ | |
| 0, /* properties_destroyed */ | |
| 0, /* todo_flags_start */ | |
| 0, /* todo_flags_finish */ | |
| }; | |
| class pass_ipa_comdats : public ipa_opt_pass_d | |
| { | |
| public: | |
| pass_ipa_comdats (gcc::context *ctxt) | |
| : ipa_opt_pass_d (pass_data_ipa_comdats, ctxt, | |
| NULL, /* generate_summary */ | |
| NULL, /* write_summary */ | |
| NULL, /* read_summary */ | |
| NULL, /* write_optimization_summary */ | |
| NULL, /* read_optimization_summary */ | |
| NULL, /* stmt_fixup */ | |
| 0, /* function_transform_todo_flags_start */ | |
| NULL, /* function_transform */ | |
| NULL) /* variable_transform */ | |
| {} | |
| /* opt_pass methods: */ | |
| bool gate (function *) final override; | |
| unsigned int execute (function *) final override { return ipa_comdats (); } | |
| }; // class pass_ipa_comdats | |
| bool | |
| pass_ipa_comdats::gate (function *) | |
| { | |
| return HAVE_COMDAT_GROUP; | |
| } | |
| } // anon namespace | |
| ipa_opt_pass_d * | |
| make_pass_ipa_comdats (gcc::context *ctxt) | |
| { | |
| return new pass_ipa_comdats (ctxt); | |
| } |