kbuild: implement CONFIG_TRIM_UNUSED_KSYMS without recursion
When CONFIG_TRIM_UNUSED_KSYMS is enabled, Kbuild recursively traverses the directory tree to determine which EXPORT_SYMBOL to trim. If an EXPORT_SYMBOL turns out to be unused by anyone, Kbuild begins the second traverse, where some source files are recompiled with their EXPORT_SYMBOL() tuned into a no-op. Linus stated negative opinions about this slowness in commits: -5cf0fd591f("Kbuild: disable TRIM_UNUSED_KSYMS option") -a555bdd0c5("Kbuild: enable TRIM_UNUSED_KSYMS again, with some guarding") We can do this better now. The final data structures of EXPORT_SYMBOL are generated by the modpost stage, so modpost can selectively emit KSYMTAB entries that are really used by modules. Commitf73edc8951("kbuild: unify two modpost invocations") is another ground-work to do this in a one-pass algorithm. With the list of modules, modpost sets sym->used if it is used by a module. modpost emits KSYMTAB only for symbols with sym->used==true. BTW, Nicolas explained why the trimming was implemented with recursion: https://lore.kernel.org/all/2o2rpn97-79nq-p7s2-nq5-8p83391473r@syhkavp.arg/ Actually, we never achieved that level of optimization where the chain reaction of trimming comes into play because: - CONFIG_LTO_CLANG cannot remove any unused symbols - CONFIG_LD_DEAD_CODE_DATA_ELIMINATION is enabled only for vmlinux, but not modules If deeper trimming is required, we need to revisit this, but I guess that is unlikely to happen. Signed-off-by: Masahiro Yamada <masahiroy@kernel.org>
This commit is contained in:
+52
-5
@@ -35,6 +35,9 @@ static bool warn_unresolved;
|
||||
|
||||
static int sec_mismatch_count;
|
||||
static bool sec_mismatch_warn_only = true;
|
||||
/* Trim EXPORT_SYMBOLs that are unused by in-tree modules */
|
||||
static bool trim_unused_exports;
|
||||
|
||||
/* ignore missing files */
|
||||
static bool ignore_missing_files;
|
||||
/* If set to 1, only warn (instead of error) about missing ns imports */
|
||||
@@ -219,6 +222,7 @@ struct symbol {
|
||||
bool weak;
|
||||
bool is_func;
|
||||
bool is_gpl_only; /* exported by EXPORT_SYMBOL_GPL */
|
||||
bool used; /* there exists a user of this symbol */
|
||||
char name[];
|
||||
};
|
||||
|
||||
@@ -1826,6 +1830,7 @@ static void check_exports(struct module *mod)
|
||||
continue;
|
||||
}
|
||||
|
||||
exp->used = true;
|
||||
s->module = exp->module;
|
||||
s->crc_valid = exp->crc_valid;
|
||||
s->crc = exp->crc;
|
||||
@@ -1849,6 +1854,23 @@ static void check_exports(struct module *mod)
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_white_list_exports(const char *white_list)
|
||||
{
|
||||
char *buf, *p, *name;
|
||||
|
||||
buf = read_text_file(white_list);
|
||||
p = buf;
|
||||
|
||||
while ((name = strsep(&p, "\n"))) {
|
||||
struct symbol *sym = find_symbol(name);
|
||||
|
||||
if (sym)
|
||||
sym->used = true;
|
||||
}
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void check_modname_len(struct module *mod)
|
||||
{
|
||||
const char *mod_name;
|
||||
@@ -1919,10 +1941,14 @@ static void add_exported_symbols(struct buffer *buf, struct module *mod)
|
||||
|
||||
/* generate struct for exported symbols */
|
||||
buf_printf(buf, "\n");
|
||||
list_for_each_entry(sym, &mod->exported_symbols, list)
|
||||
list_for_each_entry(sym, &mod->exported_symbols, list) {
|
||||
if (trim_unused_exports && !sym->used)
|
||||
continue;
|
||||
|
||||
buf_printf(buf, "KSYMTAB_%s(%s, \"%s\", \"%s\");\n",
|
||||
sym->is_func ? "FUNC" : "DATA", sym->name,
|
||||
sym->is_gpl_only ? "_gpl" : "", sym->namespace);
|
||||
}
|
||||
|
||||
if (!modversions)
|
||||
return;
|
||||
@@ -1930,6 +1956,9 @@ static void add_exported_symbols(struct buffer *buf, struct module *mod)
|
||||
/* record CRCs for exported symbols */
|
||||
buf_printf(buf, "\n");
|
||||
list_for_each_entry(sym, &mod->exported_symbols, list) {
|
||||
if (trim_unused_exports && !sym->used)
|
||||
continue;
|
||||
|
||||
if (!sym->crc_valid)
|
||||
warn("EXPORT symbol \"%s\" [%s%s] version generation failed, symbol will not be versioned.\n"
|
||||
"Is \"%s\" prototyped in <asm/asm-prototypes.h>?\n",
|
||||
@@ -2093,9 +2122,6 @@ static void write_mod_c_file(struct module *mod)
|
||||
char fname[PATH_MAX];
|
||||
int ret;
|
||||
|
||||
check_modname_len(mod);
|
||||
check_exports(mod);
|
||||
|
||||
add_header(&buf, mod);
|
||||
add_exported_symbols(&buf, mod);
|
||||
add_versions(&buf, mod);
|
||||
@@ -2187,6 +2213,9 @@ static void write_dump(const char *fname)
|
||||
if (mod->from_dump)
|
||||
continue;
|
||||
list_for_each_entry(sym, &mod->exported_symbols, list) {
|
||||
if (trim_unused_exports && !sym->used)
|
||||
continue;
|
||||
|
||||
buf_printf(&buf, "0x%08x\t%s\t%s\tEXPORT_SYMBOL%s\t%s\n",
|
||||
sym->crc, sym->name, mod->name,
|
||||
sym->is_gpl_only ? "_GPL" : "",
|
||||
@@ -2229,12 +2258,13 @@ int main(int argc, char **argv)
|
||||
{
|
||||
struct module *mod;
|
||||
char *missing_namespace_deps = NULL;
|
||||
char *unused_exports_white_list = NULL;
|
||||
char *dump_write = NULL, *files_source = NULL;
|
||||
int opt;
|
||||
LIST_HEAD(dump_lists);
|
||||
struct dump_list *dl, *dl2;
|
||||
|
||||
while ((opt = getopt(argc, argv, "ei:mnT:o:aWwENd:")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "ei:mnT:to:au:WwENd:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'e':
|
||||
external_module = true;
|
||||
@@ -2259,6 +2289,12 @@ int main(int argc, char **argv)
|
||||
case 'T':
|
||||
files_source = optarg;
|
||||
break;
|
||||
case 't':
|
||||
trim_unused_exports = true;
|
||||
break;
|
||||
case 'u':
|
||||
unused_exports_white_list = optarg;
|
||||
break;
|
||||
case 'W':
|
||||
extra_warn = true;
|
||||
break;
|
||||
@@ -2291,6 +2327,17 @@ int main(int argc, char **argv)
|
||||
if (files_source)
|
||||
read_symbols_from_files(files_source);
|
||||
|
||||
list_for_each_entry(mod, &modules, list) {
|
||||
if (mod->from_dump || mod->is_vmlinux)
|
||||
continue;
|
||||
|
||||
check_modname_len(mod);
|
||||
check_exports(mod);
|
||||
}
|
||||
|
||||
if (unused_exports_white_list)
|
||||
handle_white_list_exports(unused_exports_white_list);
|
||||
|
||||
list_for_each_entry(mod, &modules, list) {
|
||||
if (mod->from_dump)
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user