/* Offload image generation tool for PTX. Copyright (C) 2014-2015 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. */ #include "config.h" #include "system.h" #include "coretypes.h" #include "intl.h" #include #include "obstack.h" #include "diagnostic.h" #include "collect-utils.h" #include "gomp-constants.h" const char tool_name[] = "nvptx mkoffload"; #define COMMENT_PREFIX "#" typedef enum Kind { /* 0-ff used for single char tokens */ K_symbol = 0x100, /* a symbol */ K_label, /* a label defn (i.e. symbol:) */ K_ident, /* other ident */ K_dotted, /* dotted identifier */ K_number, K_string, K_comment } Kind; typedef struct Token { unsigned short kind : 12; unsigned short space : 1; /* preceded by space */ unsigned short end : 1; /* succeeded by end of line */ /* Length of token */ unsigned short len; /* Token itself */ char const *ptr; } Token; /* statement info */ typedef enum Vis { V_dot = 0, /* random pseudo */ V_var = 1, /* var decl/defn */ V_func = 2, /* func decl/defn */ V_insn = 3, /* random insn */ V_label = 4, /* label defn */ V_comment = 5, V_pred = 6, /* predicate */ V_mask = 0x7, V_global = 0x08, /* globalize */ V_weak = 0x10, /* weakly globalize */ V_no_eol = 0x20, /* no end of line */ V_prefix_comment = 0x40 /* prefixed comment */ } Vis; typedef struct Stmt { struct Stmt *next; Token *tokens; unsigned char vis; unsigned len : 12; unsigned sym : 12; } Stmt; struct id_map { id_map *next; char *ptx_name; }; static const char *read_file (FILE *); static Token *tokenize (const char *); static void write_token (FILE *, const Token *); static void write_tokens (FILE *, const Token *, unsigned, int); static Stmt *alloc_stmt (unsigned, Token *, Token *, const Token *); #define alloc_comment(S,E) alloc_stmt (V_comment, S, E, 0) #define append_stmt(V, S) ((S)->next = *(V), *(V) = (S)) static Stmt *rev_stmts (Stmt *); static void write_stmt (FILE *, const Stmt *); static void write_stmts (FILE *, const Stmt *); static Token *parse_insn (Token *); static Token *parse_list_nosemi (Token *); static Token *parse_init (Token *); static Token *parse_file (Token *); static Stmt *decls; static Stmt *vars; static Stmt *fns; 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; /* Shows if we should compile binaries for i386 instead of x86-64. */ bool target_ilp32 = false; /* Delete tempfiles. */ /* Unlink a temporary file unless requested otherwise. */ void maybe_unlink (const char *file) { if (! debug) { if (unlink_if_ordinary (file) && errno != ENOENT) fatal_error (input_location, "deleting file %s: %m", file); } else fprintf (stderr, "[Leaving %s]\n", file); } void tool_cleanup (bool) { } /* 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 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; return buffer; } /* Read a token, advancing ptr. If we read a comment, append it to the comments block. */ static Token * tokenize (const char *ptr) { unsigned alloc = 1000; unsigned num = 0; Token *toks = XNEWVEC (Token, alloc); int in_comment = 0; int not_comment = 0; for (;; num++) { const char *base; unsigned kind; int ws = 0; int eol = 0; again: base = ptr; if (in_comment) goto block_comment; switch (kind = *ptr++) { default: break; case '\n': eol = 1; /* Fall through */ case ' ': case '\t': case '\r': case '\v': /* White space */ ws = not_comment; goto again; case '/': { if (*ptr == '/') { /* line comment. Do not include trailing \n */ base += 2; for (; *ptr; ptr++) if (*ptr == '\n') break; kind = K_comment; } else if (*ptr == '*') { /* block comment */ base += 2; ptr++; block_comment: eol = in_comment; in_comment = 1; for (; *ptr; ptr++) { if (*ptr == '\n') { ptr++; break; } if (ptr[0] == '*' && ptr[1] == '/') { in_comment = 2; ptr += 2; break; } } kind = K_comment; } else break; } break; case '"': /* quoted string */ kind = K_string; while (*ptr) if (*ptr == '"') { ptr++; break; } else if (*ptr++ == '\\') ptr++; break; case '.': if (*ptr < '0' || *ptr > '9') { kind = K_dotted; ws = not_comment; goto ident; } /* FALLTHROUGH */ case '0'...'9': kind = K_number; goto ident; break; case '$': /* local labels. */ case '%': /* register names, pseudoes etc */ kind = K_ident; goto ident; case 'a'...'z': case 'A'...'Z': case '_': kind = K_symbol; /* possible symbol name */ ident: for (; *ptr; ptr++) { if (*ptr >= 'A' && *ptr <= 'Z') continue; if (*ptr >= 'a' && *ptr <= 'z') continue; if (*ptr >= '0' && *ptr <= '9') continue; if (*ptr == '_' || *ptr == '$') continue; if (*ptr == '.' && kind != K_dotted) /* Idents starting with a dot, cannot have internal dots. */ continue; if ((*ptr == '+' || *ptr == '-') && kind == K_number && (ptr[-1] == 'e' || ptr[-1] == 'E' || ptr[-1] == 'p' || ptr[-1] == 'P')) /* exponent */ continue; break; } if (*ptr == ':') { ptr++; kind = K_label; } break; } if (alloc == num) { alloc *= 2; toks = XRESIZEVEC (Token, toks, alloc); } Token *tok = toks + num; tok->kind = kind; tok->space = ws; tok->end = 0; tok->ptr = base; tok->len = ptr - base - in_comment; in_comment &= 1; not_comment = kind != K_comment; if (eol && num) tok[-1].end = 1; if (!kind) break; } return toks; } /* Write an encoded token. */ static void write_token (FILE *out, Token const *tok) { if (tok->space) fputc (' ', out); switch (tok->kind) { case K_string: { const char *c = tok->ptr + 1; size_t len = tok->len - 2; fputs ("\\\"", out); while (len) { const char *bs = (const char *)memchr (c, '\\', len); size_t l = bs ? bs - c : len; fprintf (out, "%.*s", (int)l, c); len -= l; c += l; if (bs) { fputs ("\\\\", out); len--, c++; } } fputs ("\\\"", out); } break; default: /* All other tokens shouldn't have anything magic in them */ fprintf (out, "%.*s", tok->len, tok->ptr); break; } if (tok->end) fputs ("\\n", out); } static void write_tokens (FILE *out, Token const *toks, unsigned len, int spc) { fputs ("\t\"", out); for (; len--; toks++) write_token (out, toks); if (spc) fputs (" ", out); fputs ("\"", out); } static Stmt * alloc_stmt (unsigned vis, Token *tokens, Token *end, Token const *sym) { static unsigned alloc = 0; static Stmt *heap = 0; if (!alloc) { alloc = 1000; heap = XNEWVEC (Stmt, alloc); } Stmt *stmt = heap++; alloc--; tokens->space = 0; stmt->next = 0; stmt->vis = vis; stmt->tokens = tokens; stmt->len = end - tokens; stmt->sym = sym ? sym - tokens : ~0; return stmt; } static Stmt * rev_stmts (Stmt *stmt) { Stmt *prev = 0; Stmt *next; while (stmt) { next = stmt->next; stmt->next = prev; prev = stmt; stmt = next; } return prev; } static void write_stmt (FILE *out, const Stmt *stmt) { if ((stmt->vis & V_mask) != V_comment) { write_tokens (out, stmt->tokens, stmt->len, (stmt->vis & V_mask) == V_pred); fputs (stmt->vis & V_no_eol ? "\t" : "\n", out); } } static void write_stmts (FILE *out, const Stmt *stmts) { for (; stmts; stmts = stmts->next) write_stmt (out, stmts); } static Token * parse_insn (Token *tok) { unsigned depth = 0; do { Stmt *stmt; Token *sym = 0; unsigned s = V_insn; Token *start = tok; switch (tok++->kind) { case K_comment: while (tok->kind == K_comment) tok++; stmt = alloc_comment (start, tok); append_stmt (&fns, stmt); continue; case '{': depth++; break; case '}': depth--; break; case K_label: if (tok[-1].ptr[0] != '$') sym = tok - 1; tok[-1].end = 1; s = V_label; break; case '@': tok->space = 0; if (tok->kind == '!') tok++; if (tok->kind == K_symbol) sym = tok; tok++; s = V_pred; break; default: for (; tok->kind != ';'; tok++) { if (tok->kind == ',') tok[1].space = 0; else if (tok->kind == K_symbol) sym = tok; } tok++->end = 1; break; } stmt = alloc_stmt (s, start, tok, sym); append_stmt (&fns, stmt); if (!tok[-1].end && tok[0].kind == K_comment) { stmt->vis |= V_no_eol; stmt = alloc_comment (tok, tok + 1); append_stmt (&fns, stmt); tok++; } } while (depth); return tok; } /* comma separated list of tokens */ static Token * parse_list_nosemi (Token *tok) { Token *start = tok; do if (!(++tok)->kind) break; while ((++tok)->kind == ','); tok[-1].end = 1; Stmt *stmt = alloc_stmt (V_dot, start, tok, 0); append_stmt (&decls, stmt); return tok; } #define is_keyword(T,S) \ (sizeof (S) == (T)->len && !memcmp ((T)->ptr + 1, (S), (T)->len - 1)) static Token * parse_init (Token *tok) { for (;;) { Token *start = tok; Token const *sym = 0; Stmt *stmt; if (tok->kind == K_comment) { while (tok->kind == K_comment) tok++; stmt = alloc_comment (start, tok); append_stmt (&vars, stmt); start = tok; } if (tok->kind == '{') tok[1].space = 0; for (; tok->kind != ',' && tok->kind != ';'; tok++) if (tok->kind == K_symbol) sym = tok; tok[1].space = 0; int end = tok++->kind == ';'; stmt = alloc_stmt (V_insn, start, tok, sym); append_stmt (&vars, stmt); if (!tok[-1].end && tok->kind == K_comment) { stmt->vis |= V_no_eol; stmt = alloc_comment (tok, tok + 1); append_stmt (&vars, stmt); tok++; } if (end) break; } return tok; } static Token * parse_file (Token *tok) { Stmt *comment = 0; if (tok->kind == K_comment) { Token *start = tok; while (tok->kind == K_comment) { if (strncmp (tok->ptr, ":VAR_MAP ", 9) == 0) record_id (tok->ptr + 9, &vars_tail); if (strncmp (tok->ptr, ":FUNC_MAP ", 10) == 0) record_id (tok->ptr + 10, &funcs_tail); tok++; } comment = alloc_comment (start, tok); comment->vis |= V_prefix_comment; } if (tok->kind == K_dotted) { if (is_keyword (tok, "version") || is_keyword (tok, "target") || is_keyword (tok, "address_size")) { if (comment) append_stmt (&decls, comment); tok = parse_list_nosemi (tok); } else { unsigned vis = 0; const Token *def = 0; unsigned is_decl = 0; Token *start; for (start = tok; tok->kind && tok->kind != '=' && tok->kind != K_comment && tok->kind != '{' && tok->kind != ';'; tok++) { if (is_keyword (tok, "global") || is_keyword (tok, "const")) vis |= V_var; else if (is_keyword (tok, "func") || is_keyword (tok, "entry")) vis |= V_func; else if (is_keyword (tok, "visible")) vis |= V_global; else if (is_keyword (tok, "extern")) is_decl = 1; else if (is_keyword (tok, "weak")) vis |= V_weak; if (tok->kind == '(') { tok[1].space = 0; tok[0].space = 1; } else if (tok->kind == ')' && tok[1].kind != ';') tok[1].space = 1; if (tok->kind == K_symbol) def = tok; } if (!tok->kind) { /* end of file */ if (comment) append_stmt (&fns, comment); } else if (tok->kind == '{' || tok->kind == K_comment) { /* function defn */ Stmt *stmt = alloc_stmt (vis, start, tok, def); if (comment) { append_stmt (&fns, comment); stmt->vis |= V_prefix_comment; } append_stmt (&fns, stmt); tok = parse_insn (tok); } else { int assign = tok->kind == '='; tok++->end = 1; if ((vis & V_mask) == V_var && !is_decl) { /* variable */ Stmt *stmt = alloc_stmt (vis, start, tok, def); if (comment) { append_stmt (&vars, comment); stmt->vis |= V_prefix_comment; } append_stmt (&vars, stmt); if (assign) tok = parse_init (tok); } else { /* declaration */ Stmt *stmt = alloc_stmt (vis, start, tok, 0); if (comment) { append_stmt (&decls, comment); stmt->vis |= V_prefix_comment; } append_stmt (&decls, stmt); } } } } else { /* Something strange. Ignore it. */ if (comment) append_stmt (&fns, comment); do tok++; while (tok->kind && !tok->end); } return tok; } /* 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) { const char *input = read_file (in); Token *tok = tokenize (input); unsigned int nvars = 0, nfuncs = 0; do tok = parse_file (tok); while (tok->kind); fprintf (out, "static const char ptx_code[] = \n"); write_stmts (out, rev_stmts (decls)); write_stmts (out, rev_stmts (vars)); write_stmts (out, rev_stmts (fns)); fprintf (out, ";\n\n"); fprintf (out, "static const char *var_mappings[] = {\n"); for (id_map *id = var_ids; id; id = id->next, nvars++) fprintf (out, "\t\"%s\"%s\n", id->ptx_name, id->next ? "," : ""); fprintf (out, "};\n\n"); fprintf (out, "static const char *func_mappings[] = {\n"); for (id_map *id = func_ids; id; id = id->next, nfuncs++) fprintf (out, "\t\"%s\"%s\n", id->ptx_name, id->next ? "," : ""); fprintf (out, "};\n\n"); fprintf (out, "static const void *target_data[] = {\n"); fprintf (out, " ptx_code, (void*) %u, var_mappings, (void*) %u, " "func_mappings\n", nvars, nfuncs); fprintf (out, "};\n\n"); fprintf (out, "extern void GOMP_offload_register (const void *, int, void *);\n"); fprintf (out, "extern void *__OFFLOAD_TABLE__[];\n\n"); fprintf (out, "static __attribute__((constructor)) void init (void)\n{\n"); fprintf (out, " GOMP_offload_register (__OFFLOAD_TABLE__, %d,\n", GOMP_DEVICE_NVIDIA_PTX); fprintf (out, " &target_data);\n"); fprintf (out, "};\n"); } 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); obstack_ptr_grow (&argv_obstack, target_ilp32 ? "-m32" : "-m64"); 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); 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", 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); /* 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; } struct obstack argv_obstack; obstack_init (&argv_obstack); obstack_ptr_grow (&argv_obstack, driver); obstack_ptr_grow (&argv_obstack, "-xlto"); obstack_ptr_grow (&argv_obstack, target_ilp32 ? "-m32" : "-m64"); obstack_ptr_grow (&argv_obstack, "-S"); 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 (!target_ilp32) { 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); utils_cleanup (false); return 0; }