This new pass heuristically detects symbols referenced by toplevel assembly to prevent their optimization.
Heuristics is done by comparing identifiers in assembly to known symbols. The pass is split into 2 passes, in LGEN and in WPA. There must be one pass for WPA to be able to reference any symbol. However in WPA there may be multiple symbols with the same name, so we handle those local symbols in LGEN. gcc/ChangeLog: * Makefile.in: Add ipa-asm.cc * passes.def: Add ipa_asm. * timevar.def (TV_IPA_LTO_ASM): New. * tree-pass.h (make_pass_ipa_asm_lgen): New. (make_pass_ipa_asm_wpa): * ipa-asm.cc: New file. --- gcc/Makefile.in | 1 + gcc/ipa-asm.cc | 180 ++++++++++++++++++++++++++++++++++++++++++++++++ gcc/passes.def | 2 + gcc/timevar.def | 1 + gcc/tree-pass.h | 2 + 5 files changed, 186 insertions(+) create mode 100644 gcc/ipa-asm.cc diff --git a/gcc/Makefile.in b/gcc/Makefile.in index d2744db843d..2a58fda66e0 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1581,6 +1581,7 @@ OBJS = \ ipa-ref.o \ ipa-utils.o \ ipa-strub.o \ + ipa-asm.o \ ipa.o \ ira.o \ pair-fusion.o \ diff --git a/gcc/ipa-asm.cc b/gcc/ipa-asm.cc new file mode 100644 index 00000000000..0834c8cf137 --- /dev/null +++ b/gcc/ipa-asm.cc @@ -0,0 +1,180 @@ +/* Toplevel assembly heuristics for LTO. + Copyright (C) 2025-2025 Free Software Foundation, Inc. + Contributed by Michal Jires <mji...@suse.cz> + +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/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "backend.h" +#include "tree.h" +#include "tree-pass.h" + +#include "cgraph.h" + +/* Checks all toplevel assembly contents and compares them with known symbols. + Marks those symbols with relevant flags. + Heuristics: Detects anything in assembly that looks like an identifer. + + This pass must be in WPA, otherwise we will not see all possibly referenced + symbols - if a symbol is only declared, it will not be in the callgraph if + it is only referenced from toplevel assembly. + However in WPA there may be multiple symbols with the same identifier. + The chosen solution is to handle local symbols in LGEN pass first. */ + +void +ipa_asm_heuristics () +{ + hash_map<nofree_string_hash, symtab_node *> map; + asm_node *anode = symtab->first_asm_symbol (); + if (!anode) + return; + + symtab_node* snode; + if (flag_wpa) + { + FOR_EACH_SYMBOL (snode) + if (TREE_PUBLIC (snode->decl)) + map.put (snode->asm_name (), snode); + } + else + { + FOR_EACH_SYMBOL (snode) + map.put (snode->asm_name (), snode); + } + + size_t ident_buffer_size = 128; + char* ident = (char*) xmalloc (ident_buffer_size); + + for (; anode; anode = anode->next) + { + if (TREE_CODE (anode->asm_str) != STRING_CST) + continue; + + const char *asm_str = TREE_STRING_POINTER (anode->asm_str); + int asm_len = TREE_STRING_LENGTH (anode->asm_str); + unsigned l = 0; + bool prev_is_local = false; + + for (int i = 0; i < asm_len + 1; ++i) + { + char c = 0; + if (i < asm_len) + c = asm_str[i]; + + if ('0' <= c && c <= '9') + { + if (l) + ident[l++] = c; + } + else if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_') + ident[l++] = c; + else if (l) + { + ident[l] = 0; + symtab_node **n_ = map.get (ident); + if (n_) + { + symtab_node *n = *n_; + n->referenced_from_asm = true; + /* Local symbols. */ + if (!TREE_PUBLIC (n->decl) || prev_is_local) + { + unsigned j; + ipa_ref *ref; + n->must_remain_in_tu = true; + for (j = 0; n->iterate_referring (j, ref); j++) + ref->referring->must_remain_in_tu = true; + } + } + + prev_is_local = !strcmp (ident, "local"); + + l = 0; + } + + /* Prevent overflow. */ + if (l >= ident_buffer_size) + { + ident_buffer_size *= 2; + ident = (char*) xrealloc (ident, ident_buffer_size); + } + } + } + free (ident); +} + +namespace { + +const pass_data pass_data_ipa_asm = +{ + IPA_PASS, /* type */ + "ipa-asm", /* name */ + OPTGROUP_NONE, /* optinfo_flags */ + TV_IPA_LTO_ASM, /* tv_id */ + 0, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + 0, /* todo_flags_finish */ +}; + +class pass_ipa_asm : public ipa_opt_pass_d +{ +public: + pass_ipa_asm (gcc::context *ctxt) + : ipa_opt_pass_d (pass_data_ipa_asm, 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 + { + return flag_generate_lto || flag_wpa; + } + + unsigned int execute (function *) final override + { + ipa_asm_heuristics (); + return 0; + } + +}; + +} // anon namespace + +ipa_opt_pass_d * +make_pass_ipa_asm_lgen (gcc::context *ctxt) +{ + return new pass_ipa_asm (ctxt); +} + +ipa_opt_pass_d * +make_pass_ipa_asm_wpa (gcc::context *ctxt) +{ + return new pass_ipa_asm (ctxt); +} diff --git a/gcc/passes.def b/gcc/passes.def index 68ce53baa0f..f89dd838377 100644 --- a/gcc/passes.def +++ b/gcc/passes.def @@ -154,10 +154,12 @@ along with GCC; see the file COPYING3. If not see NEXT_PASS (pass_ipa_increase_alignment); NEXT_PASS (pass_ipa_tm); NEXT_PASS (pass_ipa_lower_emutls); + NEXT_PASS (pass_ipa_asm_lgen); TERMINATE_PASS_LIST (all_small_ipa_passes) INSERT_PASSES_AFTER (all_regular_ipa_passes) NEXT_PASS (pass_analyzer); + NEXT_PASS (pass_ipa_asm_wpa); NEXT_PASS (pass_ipa_odr); NEXT_PASS (pass_ipa_whole_program_visibility); NEXT_PASS (pass_ipa_profile); diff --git a/gcc/timevar.def b/gcc/timevar.def index 4f60f04baa1..f0f4521894a 100644 --- a/gcc/timevar.def +++ b/gcc/timevar.def @@ -81,6 +81,7 @@ DEFTIMEVAR (TV_IPA_INLINING , "ipa inlining heuristics") DEFTIMEVAR (TV_IPA_FNSPLIT , "ipa function splitting") DEFTIMEVAR (TV_IPA_COMDATS , "ipa comdats") DEFTIMEVAR (TV_IPA_OPT , "ipa various optimizations") +DEFTIMEVAR (TV_IPA_LTO_ASM , "ipa lto asm heuristics") DEFTIMEVAR (TV_IPA_LTO_DECOMPRESS , "lto stream decompression") DEFTIMEVAR (TV_IPA_LTO_COMPRESS , "lto stream compression") DEFTIMEVAR (TV_IPA_LTO_OUTPUT , "lto stream output") diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h index 1c68a69350d..46c15da08e0 100644 --- a/gcc/tree-pass.h +++ b/gcc/tree-pass.h @@ -528,6 +528,8 @@ extern simple_ipa_opt_pass *make_pass_local_optimization_passes (gcc::context *c extern simple_ipa_opt_pass *make_pass_ipa_remove_symbols (gcc::context *ctxt); extern ipa_opt_pass_d *make_pass_analyzer (gcc::context *ctxt); +extern ipa_opt_pass_d *make_pass_ipa_asm_lgen (gcc::context *ctxt); +extern ipa_opt_pass_d *make_pass_ipa_asm_wpa (gcc::context *ctxt); extern ipa_opt_pass_d *make_pass_ipa_whole_program_visibility (gcc::context *ctxt); extern simple_ipa_opt_pass *make_pass_ipa_increase_alignment (gcc::context -- 2.50.0