/* LTO plugin for gold and/or GNU ld.
- Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+ Copyright (C) 2009-2020 Free Software Foundation, Inc.
Contributed by Rafael Avila de Espindola (espindola@google.com).
This program is free software; you can redistribute it and/or modify
More information at http://gcc.gnu.org/wiki/whopr/driver.
This plugin should be passed the lto-wrapper options and will forward them.
- It also has 2 options of its own:
+ It also has options at his own:
-debug: Print the command line used to run lto-wrapper.
-nop: Instead of running lto-wrapper, pass the original to the plugin. This
- only works if the input files are hybrid. */
+ only works if the input files are hybrid.
+ -linker-output-known: Do not determine linker output
+ -linker-output-auto-notlo-rel: Switch from rel to nolto-rel mode without
+ warning. This is used on systems like VxWorks (kernel) where the link is
+ always partial and repeated incremental linking is generally not used.
+ -sym-style={none,win32,underscore|uscore}
+ -pass-through */
#ifdef HAVE_CONFIG_H
#include "config.h"
#if HAVE_STDINT_H
#include <stdint.h>
#endif
+#include <stdbool.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
/* LTO magic section name. */
-#define LTO_SECTION_PREFIX ".gnu.lto_.symtab"
-#define LTO_SECTION_PREFIX_LEN (sizeof (LTO_SECTION_PREFIX) - 1)
-#define OFFLOAD_SECTION ".gnu.offload_lto_.opts"
-#define OFFLOAD_SECTION_LEN (sizeof (OFFLOAD_SECTION) - 1)
+#define LTO_SYMTAB_PREFIX ".gnu.lto_.symtab"
+#define LTO_SYMTAB_PREFIX_LEN (sizeof (LTO_SYMTAB_PREFIX) - 1)
+#define LTO_SYMTAB_EXT_PREFIX ".gnu.lto_.ext_symtab"
+#define LTO_SYMTAB_EXT_PREFIX_LEN (sizeof (LTO_SYMTAB_EXT_PREFIX) - 1)
+#define LTO_LTO_PREFIX ".gnu.lto_.lto"
+#define LTO_LTO_PREFIX_LEN (sizeof (LTO_LTO_PREFIX) - 1)
+#define OFFLOAD_SECTION ".gnu.offload_lto_.opts"
+#define OFFLOAD_SECTION_LEN (sizeof (OFFLOAD_SECTION) - 1)
/* The part of the symbol table the plugin has to keep track of. Note that we
must keep SYMS until all_symbols_read is called to give the linker time to
struct plugin_symtab conflicts;
};
+/* List item with name of the file with offloading. */
+
+struct plugin_offload_file
+{
+ char *name;
+ struct plugin_offload_file *next;
+};
+
/* Until ASM_OUTPUT_LABELREF can be hookized and decoupled from
stdio file streams, we do simple label translation here. */
static ld_plugin_add_input_file add_input_file;
static ld_plugin_add_input_library add_input_library;
static ld_plugin_message message;
-static ld_plugin_add_symbols add_symbols;
+static ld_plugin_add_symbols add_symbols, add_symbols_v2;
static struct plugin_file_info *claimed_files = NULL;
static unsigned int num_claimed_files = 0;
-
-static struct plugin_file_info *offload_files = NULL;
-static unsigned int num_offload_files = 0;
+static unsigned int non_claimed_files = 0;
+
+/* List of files with offloading. */
+static struct plugin_offload_file *offload_files;
+/* Last file in the list. */
+static struct plugin_offload_file *offload_files_last;
+/* Last non-archive file in the list. */
+static struct plugin_offload_file *offload_files_last_obj;
+/* Last LTO file in the list. */
+static struct plugin_offload_file *offload_files_last_lto;
+/* Total number of files with offloading. */
+static unsigned num_offload_files;
static char **output_files = NULL;
static unsigned int num_output_files = 0;
static char **pass_through_items = NULL;
static unsigned int num_pass_through_items;
-static char debug;
+static bool debug;
+static bool save_temps;
+static bool verbose;
static char nop;
static char *resolution_file = NULL;
+static enum ld_plugin_output_file_type linker_output;
+static bool linker_output_set;
+static bool linker_output_known;
+static bool linker_output_auto_nolto_rel;
+static const char *link_output_name = NULL;
/* The version of gold being used, or -1 if not gold. The number is
MAJOR * 100 + MINOR. */
else
entry->comdat_key = xstrdup (entry->comdat_key);
+ entry->unused = entry->section_kind = entry->symbol_type = 0;
+
t = *p;
check (t <= 4, LDPL_FATAL, "invalid symbol kind found");
entry->def = translate_kind[t];
return p;
}
+/* Parse an entry of the IL symbol table. The data to be parsed is pointed
+ by P and the result is written in ENTRY. The slot number is stored in SLOT.
+ Returns the address of the next entry. */
+
+static char *
+parse_table_entry_extension (char *p, struct ld_plugin_symbol *entry)
+{
+ unsigned char t;
+ enum ld_plugin_symbol_type symbol_types[] =
+ {
+ LDST_UNKNOWN,
+ LDST_FUNCTION,
+ LDST_VARIABLE,
+ };
+
+ t = *p;
+ check (t <= 2, LDPL_FATAL, "invalid symbol type found");
+ entry->symbol_type = symbol_types[t];
+ p++;
+ entry->section_kind = *p;
+ p++;
+
+ return p;
+}
+
+
/* Translate the IL symbol table located between DATA and END. Append the
slots and symbols to OUT. */
out->aux = aux;
}
+static void
+parse_symtab_extension (char *data, char *end, struct plugin_symtab *out)
+{
+ unsigned i;
+
+ unsigned char version = *data;
+ data++;
+
+ /* Version 1 contains the following data per entry:
+ - symbol_type
+ - section_kind
+ . */
+
+ if (version == 1)
+ for (i = 0; i < out->nsyms; i++)
+ data = parse_table_entry_extension (data, &out->syms[i]);
+}
+
/* Free all memory that is no longer needed after writing the symbol
resolution. */
free (info->name);
}
- for (i = 0; i < num_offload_files; i++)
- {
- struct plugin_file_info *info = &offload_files[i];
- struct plugin_symtab *symtab = &info->symtab;
- free (symtab->aux);
- free (info->name);
- }
-
for (i = 0; i < num_output_files; i++)
free (output_files[i]);
free (output_files);
claimed_files = NULL;
num_claimed_files = 0;
- free (offload_files);
- offload_files = NULL;
+ while (offload_files)
+ {
+ struct plugin_offload_file *ofld = offload_files;
+ offload_files = offload_files->next;
+ free (ofld);
+ }
num_offload_files = 0;
free (arguments_file_name);
for (i = 0; i < symtab->nsyms; i++)
{
- int resolution = LDPR_UNKNOWN;
+ char resolution = LDPR_UNKNOWN;
if (symtab->aux[i].next_conflict == -1)
continue;
struct pex_obj *pex;
const char *errmsg;
- /* Write argv to a file to avoid a command line that is too long. */
- arguments_file_name = make_temp_file ("");
+ /* Write argv to a file to avoid a command line that is too long
+ Save the file locally on save-temps. */
+ if (save_temps && link_output_name)
+ {
+ arguments_file_name = (char *) xmalloc (strlen (link_output_name)
+ + sizeof (".lto_wrapper_args") + 1);
+ strcpy (arguments_file_name, link_output_name);
+ strcat (arguments_file_name, ".lto_wrapper_args");
+ }
+ else
+ arguments_file_name = make_temp_file (".lto_wrapper_args");
check (arguments_file_name, LDPL_FATAL,
"Failed to generate a temorary file name");
for (i = 1; argv[i]; i++)
{
char *a = argv[i];
+ /* Check the input argument list for a verbose marker too. */
if (a[0] == '-' && a[1] == 'v' && a[2] == '\0')
{
- for (i = 0; argv[i]; i++)
- fprintf (stderr, "%s ", argv[i]);
- fprintf (stderr, "\n");
+ verbose = true;
break;
}
}
+ if (verbose)
+ {
+ for (i = 0; argv[i]; i++)
+ fprintf (stderr, "%s ", argv[i]);
+ fprintf (stderr, "\n");
+ }
+
new_argv[0] = argv[0];
new_argv[1] = at_args;
new_argv[2] = NULL;
fprintf (stderr, "\n");
}
-
pex = pex_init (PEX_USE_PIPES, "lto-wrapper", NULL);
check (pex != NULL, LDPL_FATAL, "could not pex_init lto-wrapper");
static enum ld_plugin_status
all_symbols_read_handler (void)
{
+ const unsigned num_lto_args
+ = num_claimed_files + lto_wrapper_num_args + 2
+ + !linker_output_known + !linker_output_auto_nolto_rel;
unsigned i;
- unsigned num_lto_args
- = num_claimed_files + num_offload_files + lto_wrapper_num_args + 1;
char **lto_argv;
+ const char *linker_output_str = NULL;
const char **lto_arg_ptr;
if (num_claimed_files + num_offload_files == 0)
return LDPS_OK;
write_resolution ();
free_1 (claimed_files, num_claimed_files);
- free_1 (offload_files, num_offload_files);
for (i = 0; i < lto_wrapper_num_args; i++)
*lto_arg_ptr++ = lto_wrapper_argv[i];
- for (i = 0; i < num_claimed_files; i++)
+ if (!linker_output_known)
{
- struct plugin_file_info *info = &claimed_files[i];
+ assert (linker_output_set);
+ switch (linker_output)
+ {
+ case LDPO_REL:
+ if (non_claimed_files)
+ {
+ if (!linker_output_auto_nolto_rel)
+ message (LDPL_WARNING, "incremental linking of LTO and non-LTO"
+ " objects; using -flinker-output=nolto-rel which will"
+ " bypass whole program optimization");
+ linker_output_str = "-flinker-output=nolto-rel";
+ }
+ else
+ linker_output_str = "-flinker-output=rel";
+ break;
+ case LDPO_DYN:
+ linker_output_str = "-flinker-output=dyn";
+ break;
+ case LDPO_PIE:
+ linker_output_str = "-flinker-output=pie";
+ break;
+ case LDPO_EXEC:
+ linker_output_str = "-flinker-output=exec";
+ break;
+ default:
+ message (LDPL_FATAL, "unsupported linker output %i", linker_output);
+ break;
+ }
+ *lto_arg_ptr++ = xstrdup (linker_output_str);
+ }
- *lto_arg_ptr++ = info->name;
+ if (num_offload_files > 0)
+ {
+ FILE *f;
+ char *arg;
+ char *offload_objects_file_name;
+ struct plugin_offload_file *ofld;
+
+ offload_objects_file_name = make_temp_file (".ofldlist");
+ check (offload_objects_file_name, LDPL_FATAL,
+ "Failed to generate a temporary file name");
+ f = fopen (offload_objects_file_name, "w");
+ check (f, LDPL_FATAL, "could not open file with offload objects");
+ fprintf (f, "%u\n", num_offload_files);
+
+ /* Skip the dummy item at the start of the list. */
+ ofld = offload_files->next;
+ while (ofld)
+ {
+ fprintf (f, "%s\n", ofld->name);
+ ofld = ofld->next;
+ }
+ fclose (f);
+
+ arg = concat ("-foffload-objects=", offload_objects_file_name, NULL);
+ check (arg, LDPL_FATAL, "could not allocate");
+ *lto_arg_ptr++ = arg;
}
- for (i = 0; i < num_offload_files; i++)
+ for (i = 0; i < num_claimed_files; i++)
{
- struct plugin_file_info *info = &offload_files[i];
+ struct plugin_file_info *info = &claimed_files[i];
*lto_arg_ptr++ = info->name;
}
return LDPS_OK;
}
+/* Helper, as used in collect2. */
+static int
+file_exists (const char *name)
+{
+ return access (name, R_OK) == 0;
+}
+
+/* Unlink FILE unless we have save-temps set.
+ Note that we're saving files if verbose output is set. */
+
+static void
+maybe_unlink (const char *file)
+{
+ if (save_temps && file_exists (file))
+ {
+ if (verbose)
+ fprintf (stderr, "[Leaving %s]\n", file);
+ return;
+ }
+
+ unlink_if_ordinary (file);
+}
+
/* Remove temporary files at the end of the link. */
static enum ld_plugin_status
cleanup_handler (void)
{
unsigned int i;
- int t;
if (debug)
return LDPS_OK;
if (arguments_file_name)
- {
- t = unlink (arguments_file_name);
- check (t == 0, LDPL_FATAL, "could not unlink arguments file");
- }
+ maybe_unlink (arguments_file_name);
for (i = 0; i < num_output_files; i++)
- {
- t = unlink (output_files[i]);
- check (t == 0, LDPL_FATAL, "could not unlink output file");
- }
+ maybe_unlink (output_files[i]);
free_2 ();
return LDPS_OK;
char *s;
char *secdatastart, *secdata;
- if (strncmp (name, LTO_SECTION_PREFIX, LTO_SECTION_PREFIX_LEN) != 0)
+ if (strncmp (name, LTO_SYMTAB_PREFIX, LTO_SYMTAB_PREFIX_LEN) != 0)
return 1;
s = strrchr (name, '.');
return 0;
}
+/* Process one section of an object file. */
+
+static int
+process_symtab_extension (void *data, const char *name, off_t offset,
+ off_t length)
+{
+ struct plugin_objfile *obj = (struct plugin_objfile *)data;
+ char *s;
+ char *secdatastart, *secdata;
+
+ if (strncmp (name, LTO_SYMTAB_EXT_PREFIX, LTO_SYMTAB_EXT_PREFIX_LEN) != 0)
+ return 1;
+
+ s = strrchr (name, '.');
+ if (s)
+ sscanf (s, ".%" PRI_LL "x", &obj->out->id);
+ secdata = secdatastart = xmalloc (length);
+ offset += obj->file->offset;
+ if (offset != lseek (obj->file->fd, offset, SEEK_SET))
+ goto err;
+
+ do
+ {
+ ssize_t got = read (obj->file->fd, secdata, length);
+ if (got == 0)
+ break;
+ else if (got > 0)
+ {
+ secdata += got;
+ length -= got;
+ }
+ else if (errno != EINTR)
+ goto err;
+ }
+ while (length > 0);
+ if (length > 0)
+ goto err;
+
+ parse_symtab_extension (secdatastart, secdata, obj->out);
+ obj->found++;
+ free (secdatastart);
+ return 1;
+
+err:
+ if (message)
+ message (LDPL_FATAL, "%s: corrupt object file", obj->file->name);
+ /* Force claim_file_handler to abandon this file. */
+ obj->found = 0;
+ free (secdatastart);
+ return 0;
+}
+
+
/* Find an offload section of an object file. */
static int
if (file->offset != 0)
{
- char *objname;
/* We pass the offset of the actual file, not the archive header.
Can't use PRIx64, because that's C99, so we have to print the
- 64-bit hex int as two 32-bit ones. */
- int lo, hi, t;
+ 64-bit hex int as two 32-bit ones. Use xasprintf instead of
+ asprintf because asprintf doesn't work as expected on some older
+ mingw32 hosts. */
+ int lo, hi;
lo = file->offset & 0xffffffff;
hi = ((int64_t)file->offset >> 32) & 0xffffffff;
- t = hi ? asprintf (&objname, "%s@0x%x%08x", file->name, lo, hi)
- : asprintf (&objname, "%s@0x%x", file->name, lo);
- check (t >= 0, LDPL_FATAL, "asprintf failed");
- lto_file.name = objname;
+ lto_file.name = hi ? xasprintf ("%s@0x%x%08x", file->name, hi, lo)
+ : xasprintf ("%s@0x%x", file->name, lo);
}
else
{
if (!obj.objfile && !err)
goto err;
- if (obj.objfile)
- errmsg = simple_object_find_sections (obj.objfile, process_symtab, &obj, &err);
+ if (obj.objfile)
+ {
+ errmsg = simple_object_find_sections (obj.objfile, process_symtab, &obj,
+ &err);
+ /* Parsing symtab extension should be done only for add_symbols_v2 and
+ later versions. */
+ if (!errmsg && add_symbols_v2 != NULL)
+ errmsg = simple_object_find_sections (obj.objfile,
+ process_symtab_extension,
+ &obj, &err);
+ }
if (!obj.objfile || errmsg)
{
if (obj.found > 0)
{
- status = add_symbols (file->handle, lto_file.symtab.nsyms,
- lto_file.symtab.syms);
+ if (add_symbols_v2)
+ status = add_symbols_v2 (file->handle, lto_file.symtab.nsyms,
+ lto_file.symtab.syms);
+ else
+ status = add_symbols (file->handle, lto_file.symtab.nsyms,
+ lto_file.symtab.syms);
check (status == LDPS_OK, LDPL_FATAL, "could not add symbols");
num_claimed_files++;
xrealloc (claimed_files,
num_claimed_files * sizeof (struct plugin_file_info));
claimed_files[num_claimed_files - 1] = lto_file;
+
+ *claimed = 1;
}
- if (obj.found == 0 && obj.offload == 1)
+ if (offload_files == NULL)
{
- num_offload_files++;
- offload_files =
- xrealloc (offload_files,
- num_offload_files * sizeof (struct plugin_file_info));
- offload_files[num_offload_files - 1] = lto_file;
+ /* Add dummy item to the start of the list. */
+ offload_files = xmalloc (sizeof (struct plugin_offload_file));
+ offload_files->name = NULL;
+ offload_files->next = NULL;
+ offload_files_last = offload_files;
}
- *claimed = 1;
+ /* If this is an LTO file without offload, and it is the first LTO file, save
+ the pointer to the last offload file in the list. Further offload LTO
+ files will be inserted after it, if any. */
+ if (*claimed && obj.offload == 0 && offload_files_last_lto == NULL)
+ offload_files_last_lto = offload_files_last;
+
+ if (obj.offload == 1)
+ {
+ /* Add file to the list. The order must be exactly the same as the final
+ order after recompilation and linking, otherwise host and target tables
+ with addresses wouldn't match. If a static library contains both LTO
+ and non-LTO objects, ld and gold link them in a different order. */
+ struct plugin_offload_file *ofld
+ = xmalloc (sizeof (struct plugin_offload_file));
+ ofld->name = lto_file.name;
+ ofld->next = NULL;
+
+ if (*claimed && offload_files_last_lto == NULL && file->offset != 0
+ && gold_version == -1)
+ {
+ /* ld only: insert first LTO file from the archive after the last real
+ object file immediately preceding the archive, or at the begin of
+ the list if there was no real objects before archives. */
+ if (offload_files_last_obj != NULL)
+ {
+ ofld->next = offload_files_last_obj->next;
+ offload_files_last_obj->next = ofld;
+ }
+ else
+ {
+ ofld->next = offload_files->next;
+ offload_files->next = ofld;
+ }
+ }
+ else if (*claimed && offload_files_last_lto != NULL)
+ {
+ /* Insert LTO file after the last LTO file in the list. */
+ ofld->next = offload_files_last_lto->next;
+ offload_files_last_lto->next = ofld;
+ }
+ else
+ /* Add non-LTO file or first non-archive LTO file to the end of the
+ list. */
+ offload_files_last->next = ofld;
+
+ if (ofld->next == NULL)
+ offload_files_last = ofld;
+ if (file->offset == 0)
+ offload_files_last_obj = ofld;
+ if (*claimed)
+ offload_files_last_lto = ofld;
+ num_offload_files++;
+ }
goto cleanup;
err:
+ non_claimed_files++;
free (lto_file.name);
cleanup:
static void
process_option (const char *option)
{
- if (strcmp (option, "-debug") == 0)
- debug = 1;
+ if (strcmp (option, "-linker-output-known") == 0)
+ linker_output_known = true;
+ else if (strcmp (option, "-linker-output-auto-notlo-rel") == 0)
+ linker_output_auto_nolto_rel = true;
+ else if (strcmp (option, "-debug") == 0)
+ debug = true;
+ else if ((strcmp (option, "-v") == 0)
+ || (strcmp (option, "--verbose") == 0))
+ verbose = true;
+ else if (strcmp (option, "-save-temps") == 0)
+ save_temps = true;
else if (strcmp (option, "-nop") == 0)
nop = 1;
else if (!strncmp (option, "-pass-through=", strlen("-pass-through=")))
if (strncmp (option, "-fresolution=", sizeof ("-fresolution=") - 1) == 0)
resolution_file = opt + sizeof ("-fresolution=") - 1;
}
+ save_temps = save_temps || debug;
+ verbose = verbose || debug;
}
/* Called by gold after loading the plugin. TV is the transfer vector. */
case LDPT_REGISTER_CLAIM_FILE_HOOK:
register_claim_file = p->tv_u.tv_register_claim_file;
break;
+ case LDPT_ADD_SYMBOLS_V2:
+ add_symbols_v2 = p->tv_u.tv_add_symbols;
+ break;
case LDPT_ADD_SYMBOLS:
add_symbols = p->tv_u.tv_add_symbols;
break;
case LDPT_GOLD_VERSION:
gold_version = p->tv_u.tv_val;
break;
+ case LDPT_LINKER_OUTPUT:
+ linker_output = (enum ld_plugin_output_file_type) p->tv_u.tv_val;
+ linker_output_set = true;
+ break;
+ case LDPT_OUTPUT_NAME:
+ /* We only use this to make user-friendly temp file names. */
+ link_output_name = p->tv_u.tv_string;
+ break;
default:
break;
}
"could not register the all_symbols_read callback");
}
- /* Support -fno-use-linker-plugin by failing to load the plugin
- for the case where it is auto-loaded by BFD. */
char *collect_gcc_options = getenv ("COLLECT_GCC_OPTIONS");
- if (collect_gcc_options
- && strstr (collect_gcc_options, "'-fno-use-linker-plugin'"))
- return LDPS_ERR;
+ if (collect_gcc_options)
+ {
+ /* Support -fno-use-linker-plugin by failing to load the plugin
+ for the case where it is auto-loaded by BFD. */
+ if (strstr (collect_gcc_options, "'-fno-use-linker-plugin'"))
+ return LDPS_ERR;
+
+ if ( strstr (collect_gcc_options, "'-save-temps'"))
+ save_temps = true;
+
+ if (strstr (collect_gcc_options, "'-v'")
+ || strstr (collect_gcc_options, "'--verbose'"))
+ verbose = true;
+ }
return LDPS_OK;
}