/* Offload image generation tool for PTX. Copyright (C) 2014-2020 Free Software Foundation, Inc. Contributed by Nathan Sidwell and Bernd Schmidt . 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 . */ /* Munges PTX assembly into a C source file defining the PTX code as a string. This is not a complete assembler. We presume the source is well formed from the compiler and can die horribly if it is not. */ #define IN_TARGET_CODE 1 #include "config.h" #include "system.h" #include "coretypes.h" #include "obstack.h" #include "diagnostic.h" #include "intl.h" #include #include "collect-utils.h" #include "gomp-constants.h" const char tool_name[] = "nvptx mkoffload"; #define COMMENT_PREFIX "#" struct id_map { id_map *next; char *ptx_name; }; static id_map *func_ids, **funcs_tail = &func_ids; static id_map *var_ids, **vars_tail = &var_ids; /* Files to unlink. */ static const char *ptx_name; static const char *ptx_cfile_name; enum offload_abi offload_abi = OFFLOAD_ABI_UNSET; /* Delete tempfiles. */ void tool_cleanup (bool from_signal ATTRIBUTE_UNUSED) { if (ptx_cfile_name) maybe_unlink (ptx_cfile_name); if (ptx_name) maybe_unlink (ptx_name); } static void mkoffload_cleanup (void) { tool_cleanup (false); } /* Unlink FILE unless requested otherwise. */ void maybe_unlink (const char *file) { if (!save_temps) { if (unlink_if_ordinary (file) && errno != ENOENT) fatal_error (input_location, "deleting file %s: %m", file); } else if (verbose) fprintf (stderr, "[Leaving %s]\n", 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)); } static void record_id (const char *p1, id_map ***where) { const char *end = strchr (p1, '\n'); if (!end) fatal_error (input_location, "malformed ptx file"); id_map *v = XNEW (id_map); size_t len = end - p1; v->ptx_name = XNEWVEC (char, len + 1); memcpy (v->ptx_name, p1, len); v->ptx_name[len] = '\0'; v->next = NULL; id_map **tail = *where; *tail = v; *where = &v->next; } /* Read the whole input file. It will be NUL terminated (but remember, there could be a NUL in the file itself. */ static const char * read_file (FILE *stream, size_t *plen) { size_t alloc = 16384; size_t base = 0; char *buffer; if (!fseek (stream, 0, SEEK_END)) { /* Get the file size. */ long s = ftell (stream); if (s >= 0) alloc = s + 100; fseek (stream, 0, SEEK_SET); } buffer = XNEWVEC (char, alloc); for (;;) { size_t n = fread (buffer + base, 1, alloc - base - 1, stream); if (!n) break; base += n; if (base + 1 == alloc) { alloc *= 2; buffer = XRESIZEVEC (char, buffer, alloc); } } buffer[base] = 0; *plen = base; return buffer; } /* 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); } static void process (FILE *in, FILE *out) { size_t len = 0; const char *input = read_file (in, &len); const char *comma; id_map const *id; unsigned obj_count = 0; unsigned ix; /* Dump out char arrays for each PTX object file. These are terminated by a NUL. */ for (size_t i = 0; i != len;) { char c; fprintf (out, "static const char ptx_code_%u[] =\n\t\"", obj_count++); while ((c = input[i++])) { switch (c) { case '\r': continue; case '\n': fprintf (out, "\\n\"\n\t\""); /* Look for mappings on subsequent lines. */ while (strncmp (input + i, "//:", 3) == 0) { i += 3; if (strncmp (input + i, "VAR_MAP ", 8) == 0) record_id (input + i + 8, &vars_tail); else if (strncmp (input + i, "FUNC_MAP ", 9) == 0) record_id (input + i + 9, &funcs_tail); else abort (); /* Skip to next line. */ while (input[i++] != '\n') continue; } continue; case '"': case '\\': putc ('\\', out); break; default: break; } putc (c, out); } fprintf (out, "\";\n\n"); } /* Dump out array of pointers to ptx object strings. */ fprintf (out, "static const struct ptx_obj {\n" " const char *code;\n" " __SIZE_TYPE__ size;\n" "} ptx_objs[] = {"); for (comma = "", ix = 0; ix != obj_count; comma = ",", ix++) fprintf (out, "%s\n\t{ptx_code_%u, sizeof (ptx_code_%u)}", comma, ix, ix); fprintf (out, "\n};\n\n"); /* Dump out variable idents. */ fprintf (out, "static const char *const var_mappings[] = {"); for (comma = "", id = var_ids; id; comma = ",", id = id->next) fprintf (out, "%s\n\t%s", comma, id->ptx_name); fprintf (out, "\n};\n\n"); /* Dump out function idents. */ fprintf (out, "static const struct nvptx_fn {\n" " const char *name;\n" " unsigned short dim[%d];\n" "} func_mappings[] = {\n", GOMP_DIM_MAX); for (comma = "", id = func_ids; id; comma = ",", id = id->next) fprintf (out, "%s\n\t{%s}", comma, id->ptx_name); fprintf (out, "\n};\n\n"); fprintf (out, "static const struct nvptx_tdata {\n" " const struct ptx_obj *ptx_objs;\n" " unsigned ptx_num;\n" " const char *const *var_names;\n" " unsigned var_num;\n" " const struct nvptx_fn *fn_names;\n" " unsigned fn_num;\n" "} target_data = {\n" " ptx_objs, sizeof (ptx_objs) / sizeof (ptx_objs[0]),\n" " var_mappings," " sizeof (var_mappings) / sizeof (var_mappings[0]),\n" " func_mappings," " sizeof (func_mappings) / sizeof (func_mappings[0])\n" "};\n\n"); fprintf (out, "#ifdef __cplusplus\n" "extern \"C\" {\n" "#endif\n"); fprintf (out, "extern void GOMP_offload_register_ver" " (unsigned, const void *, int, const void *);\n"); fprintf (out, "extern void GOMP_offload_unregister_ver" " (unsigned, const void *, int, const void *);\n"); fprintf (out, "#ifdef __cplusplus\n" "}\n" "#endif\n"); fprintf (out, "extern const void *const __OFFLOAD_TABLE__[];\n\n"); fprintf (out, "static __attribute__((constructor)) void init (void)\n" "{\n" " GOMP_offload_register_ver (%#x, __OFFLOAD_TABLE__," " %d/*NVIDIA_PTX*/, &target_data);\n" "};\n", GOMP_VERSION_PACK (GOMP_VERSION, GOMP_VERSION_NVIDIA_PTX), GOMP_DEVICE_NVIDIA_PTX); fprintf (out, "static __attribute__((destructor)) void fini (void)\n" "{\n" " GOMP_offload_unregister_ver (%#x, __OFFLOAD_TABLE__," " %d/*NVIDIA_PTX*/, &target_data);\n" "};\n", GOMP_VERSION_PACK (GOMP_VERSION, GOMP_VERSION_NVIDIA_PTX), GOMP_DEVICE_NVIDIA_PTX); } static void compile_native (const char *infile, const char *outfile, const char *compiler) { const char *collect_gcc_options = getenv ("COLLECT_GCC_OPTIONS"); if (!collect_gcc_options) fatal_error (input_location, "environment variable COLLECT_GCC_OPTIONS must be set"); struct obstack argv_obstack; obstack_init (&argv_obstack); obstack_ptr_grow (&argv_obstack, compiler); if (save_temps) obstack_ptr_grow (&argv_obstack, "-save-temps"); if (verbose) obstack_ptr_grow (&argv_obstack, "-v"); switch (offload_abi) { case OFFLOAD_ABI_LP64: obstack_ptr_grow (&argv_obstack, "-m64"); break; case OFFLOAD_ABI_ILP32: obstack_ptr_grow (&argv_obstack, "-m32"); break; default: gcc_unreachable (); } obstack_ptr_grow (&argv_obstack, infile); obstack_ptr_grow (&argv_obstack, "-c"); obstack_ptr_grow (&argv_obstack, "-o"); obstack_ptr_grow (&argv_obstack, outfile); obstack_ptr_grow (&argv_obstack, NULL); const char **new_argv = XOBFINISH (&argv_obstack, const char **); fork_execute (new_argv[0], CONST_CAST (char **, new_argv), true); obstack_free (&argv_obstack, NULL); } int main (int argc, char **argv) { FILE *in = stdin; FILE *out = stdout; const char *outname = 0; progname = "mkoffload"; diagnostic_initialize (global_dc, 0); if (atexit (mkoffload_cleanup) != 0) fatal_error (input_location, "atexit failed"); char *collect_gcc = getenv ("COLLECT_GCC"); if (collect_gcc == NULL) fatal_error (input_location, "COLLECT_GCC must be set."); const char *gcc_path = dirname (ASTRDUP (collect_gcc)); const char *gcc_exec = basename (ASTRDUP (collect_gcc)); size_t len = (strlen (gcc_path) + 1 + strlen (GCC_INSTALL_NAME) + 1); char *driver = XALLOCAVEC (char, len); 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. */ gcc_path = NULL; int driver_used = 0; if (gcc_path != NULL) driver_used = sprintf (driver, "%s/", gcc_path); sprintf (driver + driver_used, "%s", GCC_INSTALL_NAME); bool found = false; if (gcc_path == NULL) found = true; else if (access_check (driver, X_OK) == 0) found = true; else { /* Don't use alloca pointer with XRESIZEVEC. */ driver = NULL; /* Look in all COMPILER_PATHs for GCC_INSTALL_NAME. */ char **paths = NULL; unsigned n_paths; n_paths = parse_env_var (getenv ("COMPILER_PATH"), &paths); for (unsigned i = 0; i < n_paths; i++) { len = strlen (paths[i]) + 1 + strlen (GCC_INSTALL_NAME) + 1; driver = XRESIZEVEC (char, driver, len); sprintf (driver, "%s/%s", paths[i], GCC_INSTALL_NAME); if (access_check (driver, X_OK) == 0) { found = true; break; } } free_array_of_ptrs ((void **) paths, n_paths); } if (!found) fatal_error (input_location, "offload compiler %s not found (consider using %<-B%>)", GCC_INSTALL_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); /* Scan the argument vector. */ bool fopenmp = false; bool fopenacc = false; for (int i = 1; i < argc; i++) { #define STR "-foffload-abi=" if (strncmp (argv[i], STR, strlen (STR)) == 0) { if (strcmp (argv[i] + strlen (STR), "lp64") == 0) offload_abi = OFFLOAD_ABI_LP64; else if (strcmp (argv[i] + strlen (STR), "ilp32") == 0) offload_abi = OFFLOAD_ABI_ILP32; else fatal_error (input_location, "unrecognizable argument of option " STR); } #undef STR else if (strcmp (argv[i], "-fopenmp") == 0) fopenmp = true; else if (strcmp (argv[i], "-fopenacc") == 0) fopenacc = true; else if (strcmp (argv[i], "-save-temps") == 0) save_temps = true; else if (strcmp (argv[i], "-v") == 0) verbose = true; } if (!(fopenacc ^ fopenmp)) fatal_error (input_location, "either %<-fopenacc%> or %<-fopenmp%> " "must be set"); struct obstack argv_obstack; obstack_init (&argv_obstack); obstack_ptr_grow (&argv_obstack, driver); if (save_temps) obstack_ptr_grow (&argv_obstack, "-save-temps"); if (verbose) obstack_ptr_grow (&argv_obstack, "-v"); obstack_ptr_grow (&argv_obstack, "-xlto"); switch (offload_abi) { case OFFLOAD_ABI_LP64: obstack_ptr_grow (&argv_obstack, "-m64"); break; case OFFLOAD_ABI_ILP32: obstack_ptr_grow (&argv_obstack, "-m32"); break; default: gcc_unreachable (); } if (fopenmp) obstack_ptr_grow (&argv_obstack, "-mgomp"); for (int ix = 1; ix != argc; ix++) { if (!strcmp (argv[ix], "-o") && ix + 1 != argc) outname = argv[++ix]; else obstack_ptr_grow (&argv_obstack, argv[ix]); } ptx_cfile_name = make_temp_file (".c"); out = fopen (ptx_cfile_name, "w"); if (!out) fatal_error (input_location, "cannot open '%s'", ptx_cfile_name); /* PR libgomp/65099: Currently, we only support offloading in 64-bit configurations. */ if (offload_abi == OFFLOAD_ABI_LP64) { ptx_name = make_temp_file (".mkoffload"); obstack_ptr_grow (&argv_obstack, "-o"); obstack_ptr_grow (&argv_obstack, ptx_name); obstack_ptr_grow (&argv_obstack, NULL); const char **new_argv = XOBFINISH (&argv_obstack, const char **); char *execpath = getenv ("GCC_EXEC_PREFIX"); char *cpath = getenv ("COMPILER_PATH"); char *lpath = getenv ("LIBRARY_PATH"); unsetenv ("GCC_EXEC_PREFIX"); unsetenv ("COMPILER_PATH"); unsetenv ("LIBRARY_PATH"); fork_execute (new_argv[0], CONST_CAST (char **, new_argv), true); obstack_free (&argv_obstack, NULL); xputenv (concat ("GCC_EXEC_PREFIX=", execpath, NULL)); xputenv (concat ("COMPILER_PATH=", cpath, NULL)); xputenv (concat ("LIBRARY_PATH=", lpath, NULL)); in = fopen (ptx_name, "r"); if (!in) fatal_error (input_location, "cannot open intermediate ptx file"); process (in, out); } fclose (out); compile_native (ptx_cfile_name, outname, collect_gcc); return 0; }