On 07/21/2014 09:07 PM, Ilya Verbin wrote:
I've created a wiki page about offloading. Any improvements are welcome.
It says "Immutable Page", so I can't seem to edit it. Anyway, here are
my thoughts on the configure/make changes (should go into the TODO section):
The host and offload compilers need to be able to find each other. This
is achieved by installing the offload compiler into special locations,
and informing each about the presence of the other. All available
offload compilers must first be configured with
"--enable-as-accelerator-for=host-triplet", and installed into the same
prefix as the host compiler. Then the host compiler is built with
"--enable-offload-targets=target1,target2,..." which identifies the
offload compilers that have already been built and installed.
The install locations for the offload compilers differ from those of a
normal cross toolchain, by the following mapping:
bin/$target-gcc -> bin/$host-accel-$target-gcc
lib/gcc/$target/$ver/ -> lib/gcc/$host/$ver/accel/$target
It may be necessary to compile offload compilers with a sysroot, since
otherwise install locations for libgomp could clash (maybe that library
needs to move into lib/gcc/..?)
A target needs to provide a mkoffload tool if it wishes to be usable as
an accelerator. It is installed as one of EXTRA_PROGRAMS, and the host
lto-wrapper knows how to find it from the paths described above.
mkoffload will invoke the offload compiler in LTO mode to produce an
offload binary from the host object files, then post-process this to
produce a new object file that can be linked in with the host
executable. It can find the host compiler by examining $COLLECT_GCC, and
it must take care to clear this and certain other environment variables
when executing the offload compiler so as to not confuse it.
The object file produced with mkoffload should contain a constructor
that calls GOMP_offload_register to identify itself at run-time.
Arguments to that function are a symbol called __OPENMP_TARGET__,
provided by libgcc and unique per shared object, a table with
information about mappings between host and offload functions and
variables, and a target identifier.
[ Note: on gomp-4_0-branch, all the Makefile bits aren't entirely
working at the moment since we were intending to use a slightly
different scheme for nvptx. However, this is how it should work after I
get some patches installed. ]
For reference, I'm attaching my current version of ptx mkoffload.
Bernd
/* Offload image generation tool for ptx
Nathan Sidwell <nat...@codesourcery.com>
Bernd Schmidt <ber...@codesourcery.com>
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 <libgen.h>
#include "obstack.h"
#include "diagnostic-core.h"
#include "collect-utils.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;
/* 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 ("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 ("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);
while (tok->kind && !tok->end)
tok++;
}
return tok;
}
static void
process (FILE *in, FILE *out)
{
const char *input = read_file (in);
Token *tok = tokenize (input);
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)
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)
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, var_mappings, func_mappings\n");
fprintf (out, "};\n\n");
fprintf (out, "extern void GOMP_offload_register (const void *, int, void
*);\n");
fprintf (out, "extern void *__OPENMP_TARGET__[];\n\n");
fprintf (out, "#define PTX_ID 1\n");
fprintf (out, "static __attribute__((constructor)) void init (void)\n{\n");
fprintf (out, " GOMP_offload_register (__OPENMP_TARGET__, PTX_ID,\n");
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 ("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, infile);
obstack_ptr_grow (&argv_obstack, "-c");
obstack_ptr_grow (&argv_obstack, "-o");
obstack_ptr_grow (&argv_obstack, outfile);
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;
char *collect_gcc = getenv ("COLLECT_GCC");
if (collect_gcc == NULL)
fatal_error ("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 (DEFAULT_REAL_TARGET_MACHINE)
+ strlen (DEFAULT_TARGET_MACHINE)
+ strlen ("-accel--gcc") + 1
+ strlen (gcc_path) + 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-accel-%s-gcc",
DEFAULT_REAL_TARGET_MACHINE, DEFAULT_TARGET_MACHINE);
/* 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);
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, "-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_name = make_temp_file (".mkoffload");
obstack_ptr_grow (&argv_obstack, "-o");
obstack_ptr_grow (&argv_obstack, ptx_name);
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 ("cannot open intermediate ptx file");
ptx_cfile_name = make_temp_file (".c");
out = fopen (ptx_cfile_name, "w");
if (!out)
fatal_error ("cannot open '%s'", ptx_cfile_name);
process (in, out);
fclose (out);
compile_native (ptx_cfile_name, outname, collect_gcc);
utils_cleanup (false);
return 0;
}