/* Offload image generation tool for Intel MIC devices. Copyright (C) 2014-2015 Free Software Foundation, Inc. Contributed by Ilya Verbin . 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 . */ #include "config.h" #include #include "system.h" #include "coretypes.h" #include "obstack.h" #include "intl.h" #include "diagnostic.h" #include "collect-utils.h" #include "intelmic-offload.h" const char tool_name[] = "intelmic mkoffload"; const char image_section_name[] = ".gnu.offload_images"; const char *symbols[3] = { "__offload_image_intelmic_start", "__offload_image_intelmic_end", "__offload_image_intelmic_size" }; const char *out_obj_filename = NULL; int num_temps = 0; const int MAX_NUM_TEMPS = 10; const char *temp_files[MAX_NUM_TEMPS]; /* Shows if we should compile binaries for i386 instead of x86-64. */ bool target_ilp32 = false; /* Delete tempfiles and exit function. */ void tool_cleanup (bool from_signal ATTRIBUTE_UNUSED) { for (int i = 0; i < num_temps; i++) maybe_unlink (temp_files[i]); } static void mkoffload_atexit (void) { tool_cleanup (false); } /* Unlink FILE unless we are debugging. */ void maybe_unlink (const char *file) { if (debug) notice ("[Leaving %s]\n", file); else unlink_if_ordinary (file); } /* Add or change the value of an environment variable, outputting the change to standard error if in verbose mode. */ static void xputenv (const char *string) { if (verbose) fprintf (stderr, "%s\n", string); putenv (CONST_CAST (char *, string)); } /* Parse STR, saving found tokens into PVALUES and return their number. Tokens are assumed to be delimited by ':'. */ static unsigned parse_env_var (const char *str, char ***pvalues) { const char *curval, *nextval; char **values; unsigned num = 1, i; curval = strchr (str, ':'); while (curval) { num++; curval = strchr (curval + 1, ':'); } values = (char **) xmalloc (num * sizeof (char *)); curval = str; nextval = strchr (curval, ':'); if (nextval == NULL) nextval = strchr (curval, '\0'); for (i = 0; i < num; i++) { int l = nextval - curval; values[i] = (char *) xmalloc (l + 1); memcpy (values[i], curval, l); values[i][l] = 0; curval = nextval + 1; nextval = strchr (curval, ':'); if (nextval == NULL) nextval = strchr (curval, '\0'); } *pvalues = values; return num; } /* Auxiliary function that frees elements of PTR and PTR itself. N is number of elements to be freed. If PTR is NULL, nothing is freed. If an element is NULL, subsequent elements are not freed. */ static void free_array_of_ptrs (void **ptr, unsigned n) { unsigned i; if (!ptr) return; for (i = 0; i < n; i++) { if (!ptr[i]) break; free (ptr[i]); } free (ptr); return; } /* Check whether NAME can be accessed in MODE. This is like access, except that it never considers directories to be executable. */ static int access_check (const char *name, int mode) { if (mode == X_OK) { struct stat st; if (stat (name, &st) < 0 || S_ISDIR (st.st_mode)) return -1; } return access (name, mode); } /* Find target compiler using a path from COLLECT_GCC or COMPILER_PATH. */ static char * find_target_compiler (const char *name) { bool found = false; char **paths = NULL; unsigned n_paths, i; char *target_compiler; const char *collect_gcc = getenv ("COLLECT_GCC"); const char *gcc_path = dirname (ASTRDUP (collect_gcc)); const char *gcc_exec = basename (ASTRDUP (collect_gcc)); if (strcmp (gcc_exec, collect_gcc) == 0) { /* collect_gcc has no path, so it was found in PATH. Make sure we also find accel-gcc in PATH. */ target_compiler = XDUPVEC (char, name, strlen (name) + 1); found = true; goto out; } target_compiler = concat (gcc_path, "/", name, NULL); if (access_check (target_compiler, X_OK) == 0) { found = true; goto out; } n_paths = parse_env_var (getenv ("COMPILER_PATH"), &paths); for (i = 0; i < n_paths; i++) { size_t len = strlen (paths[i]) + 1 + strlen (name) + 1; target_compiler = XRESIZEVEC (char, target_compiler, len); sprintf (target_compiler, "%s/%s", paths[i], name); if (access_check (target_compiler, X_OK) == 0) { found = true; break; } } out: free_array_of_ptrs ((void **) paths, n_paths); return found ? target_compiler : NULL; } static void compile_for_target (struct obstack *argv_obstack) { if (target_ilp32) obstack_ptr_grow (argv_obstack, "-m32"); else obstack_ptr_grow (argv_obstack, "-m64"); obstack_ptr_grow (argv_obstack, NULL); char **argv = XOBFINISH (argv_obstack, char **); /* Save environment variables. */ const char *epath = getenv ("GCC_EXEC_PREFIX"); const char *cpath = getenv ("COMPILER_PATH"); const char *lpath = getenv ("LIBRARY_PATH"); const char *rpath = getenv ("LD_RUN_PATH"); unsetenv ("GCC_EXEC_PREFIX"); unsetenv ("COMPILER_PATH"); unsetenv ("LIBRARY_PATH"); unsetenv ("LD_RUN_PATH"); fork_execute (argv[0], argv, false); obstack_free (argv_obstack, NULL); /* Restore environment variables. */ xputenv (concat ("GCC_EXEC_PREFIX=", epath, NULL)); xputenv (concat ("COMPILER_PATH=", cpath, NULL)); xputenv (concat ("LIBRARY_PATH=", lpath, NULL)); xputenv (concat ("LD_RUN_PATH=", rpath, NULL)); } /* Generates object file with the descriptor for the target library. */ static const char * generate_target_descr_file (const char *target_compiler) { const char *src_filename = make_temp_file ("_target_descr.c"); const char *obj_filename = make_temp_file ("_target_descr.o"); temp_files[num_temps++] = src_filename; temp_files[num_temps++] = obj_filename; FILE *src_file = fopen (src_filename, "w"); if (!src_file) fatal_error (input_location, "cannot open '%s'", src_filename); fprintf (src_file, "extern void *__offload_funcs_end[];\n" "extern void *__offload_vars_end[];\n\n" "void *__offload_func_table[0]\n" "__attribute__ ((__used__, visibility (\"hidden\"),\n" "section (\".gnu.offload_funcs\"))) = { };\n\n" "void *__offload_var_table[0]\n" "__attribute__ ((__used__, visibility (\"hidden\"),\n" "section (\".gnu.offload_vars\"))) = { };\n\n" "void *__OFFLOAD_TARGET_TABLE__[]\n" "__attribute__ ((__used__, visibility (\"hidden\"))) = {\n" " &__offload_func_table, &__offload_funcs_end,\n" " &__offload_var_table, &__offload_vars_end\n" "};\n\n"); fprintf (src_file, "#ifdef __cplusplus\n" "extern \"C\"\n" "#endif\n" "void target_register_lib (const void *);\n\n" "__attribute__((constructor))\n" "static void\n" "init (void)\n" "{\n" " target_register_lib (__OFFLOAD_TARGET_TABLE__);\n" "}\n"); fclose (src_file); struct obstack argv_obstack; obstack_init (&argv_obstack); obstack_ptr_grow (&argv_obstack, target_compiler); obstack_ptr_grow (&argv_obstack, "-c"); obstack_ptr_grow (&argv_obstack, "-shared"); obstack_ptr_grow (&argv_obstack, "-fPIC"); obstack_ptr_grow (&argv_obstack, src_filename); obstack_ptr_grow (&argv_obstack, "-o"); obstack_ptr_grow (&argv_obstack, obj_filename); compile_for_target (&argv_obstack); return obj_filename; } /* Generates object file with __offload_*_end symbols for the target library. */ static const char * generate_target_offloadend_file (const char *target_compiler) { const char *src_filename = make_temp_file ("_target_offloadend.c"); const char *obj_filename = make_temp_file ("_target_offloadend.o"); temp_files[num_temps++] = src_filename; temp_files[num_temps++] = obj_filename; FILE *src_file = fopen (src_filename, "w"); if (!src_file) fatal_error (input_location, "cannot open '%s'", src_filename); fprintf (src_file, "void *__offload_funcs_end[0]\n" "__attribute__ ((__used__, visibility (\"hidden\"),\n" "section (\".gnu.offload_funcs\"))) = { };\n\n" "void *__offload_vars_end[0]\n" "__attribute__ ((__used__, visibility (\"hidden\"),\n" "section (\".gnu.offload_vars\"))) = { };\n"); fclose (src_file); struct obstack argv_obstack; obstack_init (&argv_obstack); obstack_ptr_grow (&argv_obstack, target_compiler); obstack_ptr_grow (&argv_obstack, "-c"); obstack_ptr_grow (&argv_obstack, "-shared"); obstack_ptr_grow (&argv_obstack, "-fPIC"); obstack_ptr_grow (&argv_obstack, src_filename); obstack_ptr_grow (&argv_obstack, "-o"); obstack_ptr_grow (&argv_obstack, obj_filename); compile_for_target (&argv_obstack); return obj_filename; } /* Generates object file with the host side descriptor. */ static const char * generate_host_descr_file (const char *host_compiler) { const char *src_filename = make_temp_file ("_host_descr.c"); const char *obj_filename = make_temp_file ("_host_descr.o"); temp_files[num_temps++] = src_filename; temp_files[num_temps++] = obj_filename; FILE *src_file = fopen (src_filename, "w"); if (!src_file) fatal_error (input_location, "cannot open '%s'", src_filename); fprintf (src_file, "extern void *__OFFLOAD_TABLE__;\n" "extern void *__offload_image_intelmic_start;\n" "extern void *__offload_image_intelmic_end;\n\n" "static const void *__offload_target_data[] = {\n" " &__offload_image_intelmic_start, &__offload_image_intelmic_end\n" "};\n\n"); fprintf (src_file, "#ifdef __cplusplus\n" "extern \"C\"\n" "#endif\n" "void GOMP_offload_register (void *, int, void *);\n" "#ifdef __cplusplus\n" "extern \"C\"\n" "#endif\n" "void GOMP_offload_unregister (void *, int, void *);\n\n" "__attribute__((constructor))\n" "static void\n" "init (void)\n" "{\n" " GOMP_offload_register (&__OFFLOAD_TABLE__, %d, __offload_target_data);\n" "}\n\n", GOMP_DEVICE_INTEL_MIC); fprintf (src_file, "__attribute__((destructor))\n" "static void\n" "fini (void)\n" "{\n" " GOMP_offload_unregister (&__OFFLOAD_TABLE__, %d, __offload_target_data);\n" "}\n", GOMP_DEVICE_INTEL_MIC); fclose (src_file); unsigned new_argc = 0; const char *new_argv[9]; new_argv[new_argc++] = host_compiler; new_argv[new_argc++] = "-c"; new_argv[new_argc++] = "-fPIC"; new_argv[new_argc++] = "-shared"; if (target_ilp32) new_argv[new_argc++] = "-m32"; else new_argv[new_argc++] = "-m64"; new_argv[new_argc++] = src_filename; new_argv[new_argc++] = "-o"; new_argv[new_argc++] = obj_filename; new_argv[new_argc++] = NULL; fork_execute (new_argv[0], CONST_CAST (char **, new_argv), false); return obj_filename; } static const char * prepare_target_image (const char *target_compiler, int argc, char **argv) { const char *target_descr_filename = generate_target_descr_file (target_compiler); const char *target_offloadend_filename = generate_target_offloadend_file (target_compiler); char *opt1 = XALLOCAVEC (char, sizeof ("-Wl,") + strlen (target_descr_filename)); char *opt2 = XALLOCAVEC (char, sizeof ("-Wl,") + strlen (target_offloadend_filename)); sprintf (opt1, "-Wl,%s", target_descr_filename); sprintf (opt2, "-Wl,%s", target_offloadend_filename); const char *target_so_filename = make_temp_file ("_offload_intelmic.so"); temp_files[num_temps++] = target_so_filename; struct obstack argv_obstack; obstack_init (&argv_obstack); obstack_ptr_grow (&argv_obstack, target_compiler); obstack_ptr_grow (&argv_obstack, "-xlto"); obstack_ptr_grow (&argv_obstack, "-shared"); obstack_ptr_grow (&argv_obstack, "-fPIC"); obstack_ptr_grow (&argv_obstack, opt1); for (int i = 1; i < argc; i++) { if (!strcmp (argv[i], "-o") && i + 1 != argc) out_obj_filename = argv[++i]; else obstack_ptr_grow (&argv_obstack, argv[i]); } if (!out_obj_filename) fatal_error (input_location, "output file not specified"); obstack_ptr_grow (&argv_obstack, opt2); obstack_ptr_grow (&argv_obstack, "-o"); obstack_ptr_grow (&argv_obstack, target_so_filename); compile_for_target (&argv_obstack); /* Run objcopy. */ char *rename_section_opt = XALLOCAVEC (char, sizeof (".data=") + strlen (image_section_name)); sprintf (rename_section_opt, ".data=%s", image_section_name); const char *objcopy_argv[11]; objcopy_argv[0] = "objcopy"; objcopy_argv[1] = "-B"; objcopy_argv[2] = "i386"; objcopy_argv[3] = "-I"; objcopy_argv[4] = "binary"; objcopy_argv[5] = "-O"; if (target_ilp32) objcopy_argv[6] = "elf32-i386"; else objcopy_argv[6] = "elf64-x86-64"; objcopy_argv[7] = target_so_filename; objcopy_argv[8] = "--rename-section"; objcopy_argv[9] = rename_section_opt; objcopy_argv[10] = NULL; fork_execute (objcopy_argv[0], CONST_CAST (char **, objcopy_argv), false); /* Objcopy has created symbols, containing the input file name with non-alphanumeric characters replaced by underscores. We are going to rename these new symbols. */ size_t symbol_name_len = strlen (target_so_filename); char *symbol_name = XALLOCAVEC (char, symbol_name_len + 1); for (size_t i = 0; i < symbol_name_len; i++) { char c = target_so_filename[i]; if (!ISALNUM (c)) c = '_'; symbol_name[i] = c; } symbol_name[symbol_name_len] = '\0'; char *opt_for_objcopy[3]; opt_for_objcopy[0] = XALLOCAVEC (char, sizeof ("_binary__start=") + symbol_name_len + strlen (symbols[0])); opt_for_objcopy[1] = XALLOCAVEC (char, sizeof ("_binary__end=") + symbol_name_len + strlen (symbols[1])); opt_for_objcopy[2] = XALLOCAVEC (char, sizeof ("_binary__size=") + symbol_name_len + strlen (symbols[2])); sprintf (opt_for_objcopy[0], "_binary_%s_start=%s", symbol_name, symbols[0]); sprintf (opt_for_objcopy[1], "_binary_%s_end=%s", symbol_name, symbols[1]); sprintf (opt_for_objcopy[2], "_binary_%s_size=%s", symbol_name, symbols[2]); objcopy_argv[0] = "objcopy"; objcopy_argv[1] = target_so_filename; objcopy_argv[2] = "--redefine-sym"; objcopy_argv[3] = opt_for_objcopy[0]; objcopy_argv[4] = "--redefine-sym"; objcopy_argv[5] = opt_for_objcopy[1]; objcopy_argv[6] = "--redefine-sym"; objcopy_argv[7] = opt_for_objcopy[2]; objcopy_argv[8] = NULL; fork_execute (objcopy_argv[0], CONST_CAST (char **, objcopy_argv), false); return target_so_filename; } int main (int argc, char **argv) { progname = "mkoffload-intelmic"; gcc_init_libintl (); diagnostic_initialize (global_dc, 0); if (atexit (mkoffload_atexit) != 0) fatal_error (input_location, "atexit failed"); const char *host_compiler = getenv ("COLLECT_GCC"); if (!host_compiler) fatal_error (input_location, "COLLECT_GCC must be set"); const char *target_driver_name = GCC_INSTALL_NAME; char *target_compiler = find_target_compiler (target_driver_name); if (target_compiler == NULL) fatal_error (input_location, "offload compiler %s not found", target_driver_name); /* We may be called with all the arguments stored in some file and passed with @file. Expand them into argv before processing. */ expandargv (&argc, &argv); /* Find out whether we should compile binaries for i386 or x86-64. */ for (int i = argc - 1; i > 0; i--) if (strncmp (argv[i], "-foffload-abi=", sizeof ("-foffload-abi=") - 1) == 0) { if (strstr (argv[i], "ilp32")) target_ilp32 = true; else if (!strstr (argv[i], "lp64")) fatal_error (input_location, "unrecognizable argument of option -foffload-abi"); break; } const char *target_so_filename = prepare_target_image (target_compiler, argc, argv); const char *host_descr_filename = generate_host_descr_file (host_compiler); /* Perform partial linking for the target image and host side descriptor. As a result we'll get a finalized object file with all offload data. */ unsigned new_argc = 0; const char *new_argv[9]; new_argv[new_argc++] = "ld"; new_argv[new_argc++] = "-m"; if (target_ilp32) new_argv[new_argc++] = "elf_i386"; else new_argv[new_argc++] = "elf_x86_64"; new_argv[new_argc++] = "--relocatable"; new_argv[new_argc++] = host_descr_filename; new_argv[new_argc++] = target_so_filename; new_argv[new_argc++] = "-o"; new_argv[new_argc++] = out_obj_filename; new_argv[new_argc++] = NULL; fork_execute (new_argv[0], CONST_CAST (char **, new_argv), false); /* Run objcopy on the resultant object file to localize generated symbols to avoid conflicting between different DSO and an executable. */ new_argv[0] = "objcopy"; new_argv[1] = "-L"; new_argv[2] = symbols[0]; new_argv[3] = "-L"; new_argv[4] = symbols[1]; new_argv[5] = "-L"; new_argv[6] = symbols[2]; new_argv[7] = out_obj_filename; new_argv[8] = NULL; fork_execute (new_argv[0], CONST_CAST (char **, new_argv), false); return 0; }