xref: /netbsd-src/external/gpl3/gcc.old/dist/gcc/config/nvptx/mkoffload.c (revision 8feb0f0b7eaff0608f8350bbfa3098827b4bb91b)
1 /* Offload image generation tool for PTX.
2 
3    Copyright (C) 2014-2020 Free Software Foundation, Inc.
4 
5    Contributed by Nathan Sidwell <nathan@codesourcery.com> and
6    Bernd Schmidt <bernds@codesourcery.com>.
7 
8    This file is part of GCC.
9 
10    GCC is free software; you can redistribute it and/or modify it
11    under the terms of the GNU General Public License as published
12    by the Free Software Foundation; either version 3, or (at your
13    option) any later version.
14 
15    GCC is distributed in the hope that it will be useful, but WITHOUT
16    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
18    License for more details.
19 
20    You should have received a copy of the GNU General Public License
21    along with GCC; see the file COPYING3.  If not see
22    <http://www.gnu.org/licenses/>.  */
23 
24 /* Munges PTX assembly into a C source file defining the PTX code as a
25    string.
26 
27    This is not a complete assembler.  We presume the source is well
28    formed from the compiler and can die horribly if it is not.  */
29 
30 #define IN_TARGET_CODE 1
31 
32 #include "config.h"
33 #include "system.h"
34 #include "coretypes.h"
35 #include "obstack.h"
36 #include "diagnostic.h"
37 #include "intl.h"
38 #include <libgen.h>
39 #include "collect-utils.h"
40 #include "gomp-constants.h"
41 
42 const char tool_name[] = "nvptx mkoffload";
43 
44 #define COMMENT_PREFIX "#"
45 
46 struct id_map
47 {
48   id_map *next;
49   char *ptx_name;
50 };
51 
52 static id_map *func_ids, **funcs_tail = &func_ids;
53 static id_map *var_ids, **vars_tail = &var_ids;
54 
55 /* Files to unlink.  */
56 static const char *ptx_name;
57 static const char *ptx_cfile_name;
58 
59 enum offload_abi offload_abi = OFFLOAD_ABI_UNSET;
60 
61 /* Delete tempfiles.  */
62 
63 void
tool_cleanup(bool from_signal ATTRIBUTE_UNUSED)64 tool_cleanup (bool from_signal ATTRIBUTE_UNUSED)
65 {
66   if (ptx_cfile_name)
67     maybe_unlink (ptx_cfile_name);
68   if (ptx_name)
69     maybe_unlink (ptx_name);
70 }
71 
72 static void
mkoffload_cleanup(void)73 mkoffload_cleanup (void)
74 {
75   tool_cleanup (false);
76 }
77 
78 /* Unlink FILE unless requested otherwise.  */
79 
80 void
maybe_unlink(const char * file)81 maybe_unlink (const char *file)
82 {
83   if (!save_temps)
84     {
85       if (unlink_if_ordinary (file)
86 	  && errno != ENOENT)
87 	fatal_error (input_location, "deleting file %s: %m", file);
88     }
89   else if (verbose)
90     fprintf (stderr, "[Leaving %s]\n", file);
91 }
92 
93 /* Add or change the value of an environment variable, outputting the
94    change to standard error if in verbose mode.  */
95 static void
xputenv(const char * string)96 xputenv (const char *string)
97 {
98   if (verbose)
99     fprintf (stderr, "%s\n", string);
100   putenv (CONST_CAST (char *, string));
101 }
102 
103 
104 static void
record_id(const char * p1,id_map *** where)105 record_id (const char *p1, id_map ***where)
106 {
107   const char *end = strchr (p1, '\n');
108   if (!end)
109     fatal_error (input_location, "malformed ptx file");
110 
111   id_map *v = XNEW (id_map);
112   size_t len = end - p1;
113   v->ptx_name = XNEWVEC (char, len + 1);
114   memcpy (v->ptx_name, p1, len);
115   v->ptx_name[len] = '\0';
116   v->next = NULL;
117   id_map **tail = *where;
118   *tail = v;
119   *where = &v->next;
120 }
121 
122 /* Read the whole input file.  It will be NUL terminated (but
123    remember, there could be a NUL in the file itself.  */
124 
125 static const char *
read_file(FILE * stream,size_t * plen)126 read_file (FILE *stream, size_t *plen)
127 {
128   size_t alloc = 16384;
129   size_t base = 0;
130   char *buffer;
131 
132   if (!fseek (stream, 0, SEEK_END))
133     {
134       /* Get the file size.  */
135       long s = ftell (stream);
136       if (s >= 0)
137 	alloc = s + 100;
138       fseek (stream, 0, SEEK_SET);
139     }
140   buffer = XNEWVEC (char, alloc);
141 
142   for (;;)
143     {
144       size_t n = fread (buffer + base, 1, alloc - base - 1, stream);
145 
146       if (!n)
147 	break;
148       base += n;
149       if (base + 1 == alloc)
150 	{
151 	  alloc *= 2;
152 	  buffer = XRESIZEVEC (char, buffer, alloc);
153 	}
154     }
155   buffer[base] = 0;
156   *plen = base;
157   return buffer;
158 }
159 
160 /* Parse STR, saving found tokens into PVALUES and return their number.
161    Tokens are assumed to be delimited by ':'.  */
162 static unsigned
parse_env_var(const char * str,char *** pvalues)163 parse_env_var (const char *str, char ***pvalues)
164 {
165   const char *curval, *nextval;
166   char **values;
167   unsigned num = 1, i;
168 
169   curval = strchr (str, ':');
170   while (curval)
171     {
172       num++;
173       curval = strchr (curval + 1, ':');
174     }
175 
176   values = (char **) xmalloc (num * sizeof (char *));
177   curval = str;
178   nextval = strchr (curval, ':');
179   if (nextval == NULL)
180     nextval = strchr (curval, '\0');
181 
182   for (i = 0; i < num; i++)
183     {
184       int l = nextval - curval;
185       values[i] = (char *) xmalloc (l + 1);
186       memcpy (values[i], curval, l);
187       values[i][l] = 0;
188       curval = nextval + 1;
189       nextval = strchr (curval, ':');
190       if (nextval == NULL)
191 	nextval = strchr (curval, '\0');
192     }
193   *pvalues = values;
194   return num;
195 }
196 
197 /* Auxiliary function that frees elements of PTR and PTR itself.
198    N is number of elements to be freed.  If PTR is NULL, nothing is freed.
199    If an element is NULL, subsequent elements are not freed.  */
200 static void
free_array_of_ptrs(void ** ptr,unsigned n)201 free_array_of_ptrs (void **ptr, unsigned n)
202 {
203   unsigned i;
204   if (!ptr)
205     return;
206   for (i = 0; i < n; i++)
207     {
208       if (!ptr[i])
209 	break;
210       free (ptr[i]);
211     }
212   free (ptr);
213   return;
214 }
215 
216 /* Check whether NAME can be accessed in MODE.  This is like access,
217    except that it never considers directories to be executable.  */
218 static int
access_check(const char * name,int mode)219 access_check (const char *name, int mode)
220 {
221   if (mode == X_OK)
222     {
223       struct stat st;
224 
225       if (stat (name, &st) < 0 || S_ISDIR (st.st_mode))
226 	return -1;
227     }
228 
229   return access (name, mode);
230 }
231 
232 static void
process(FILE * in,FILE * out)233 process (FILE *in, FILE *out)
234 {
235   size_t len = 0;
236   const char *input = read_file (in, &len);
237   const char *comma;
238   id_map const *id;
239   unsigned obj_count = 0;
240   unsigned ix;
241 
242   /* Dump out char arrays for each PTX object file.  These are
243      terminated by a NUL.  */
244   for (size_t i = 0; i != len;)
245     {
246       char c;
247 
248       fprintf (out, "static const char ptx_code_%u[] =\n\t\"", obj_count++);
249       while ((c = input[i++]))
250 	{
251 	  switch (c)
252 	    {
253 	    case '\r':
254 	      continue;
255 	    case '\n':
256 	      fprintf (out, "\\n\"\n\t\"");
257 	      /* Look for mappings on subsequent lines.  */
258 	      while (strncmp (input + i, "//:", 3) == 0)
259 		{
260 		  i += 3;
261 
262 		  if (strncmp (input + i, "VAR_MAP ", 8) == 0)
263 		    record_id (input + i + 8, &vars_tail);
264 		  else if (strncmp (input + i, "FUNC_MAP ", 9) == 0)
265 		    record_id (input + i + 9, &funcs_tail);
266 		  else
267 		    abort ();
268 		  /* Skip to next line. */
269 		  while (input[i++] != '\n')
270 		    continue;
271 		}
272 	      continue;
273 	    case '"':
274 	    case '\\':
275 	      putc ('\\', out);
276 	      break;
277 	    default:
278 	      break;
279 	    }
280 	  putc (c, out);
281 	}
282       fprintf (out, "\";\n\n");
283     }
284 
285   /* Dump out array of pointers to ptx object strings.  */
286   fprintf (out, "static const struct ptx_obj {\n"
287 	   "  const char *code;\n"
288 	   "  __SIZE_TYPE__ size;\n"
289 	   "} ptx_objs[] = {");
290   for (comma = "", ix = 0; ix != obj_count; comma = ",", ix++)
291     fprintf (out, "%s\n\t{ptx_code_%u, sizeof (ptx_code_%u)}", comma, ix, ix);
292   fprintf (out, "\n};\n\n");
293 
294   /* Dump out variable idents.  */
295   fprintf (out, "static const char *const var_mappings[] = {");
296   for (comma = "", id = var_ids; id; comma = ",", id = id->next)
297     fprintf (out, "%s\n\t%s", comma, id->ptx_name);
298   fprintf (out, "\n};\n\n");
299 
300   /* Dump out function idents.  */
301   fprintf (out, "static const struct nvptx_fn {\n"
302 	   "  const char *name;\n"
303 	   "  unsigned short dim[%d];\n"
304 	   "} func_mappings[] = {\n", GOMP_DIM_MAX);
305   for (comma = "", id = func_ids; id; comma = ",", id = id->next)
306     fprintf (out, "%s\n\t{%s}", comma, id->ptx_name);
307   fprintf (out, "\n};\n\n");
308 
309   fprintf (out,
310 	   "static const struct nvptx_tdata {\n"
311 	   "  const struct ptx_obj *ptx_objs;\n"
312 	   "  unsigned ptx_num;\n"
313 	   "  const char *const *var_names;\n"
314 	   "  unsigned var_num;\n"
315 	   "  const struct nvptx_fn *fn_names;\n"
316 	   "  unsigned fn_num;\n"
317 	   "} target_data = {\n"
318 	   "  ptx_objs, sizeof (ptx_objs) / sizeof (ptx_objs[0]),\n"
319 	   "  var_mappings,"
320 	   "  sizeof (var_mappings) / sizeof (var_mappings[0]),\n"
321 	   "  func_mappings,"
322 	   "  sizeof (func_mappings) / sizeof (func_mappings[0])\n"
323 	   "};\n\n");
324 
325   fprintf (out, "#ifdef __cplusplus\n"
326 	   "extern \"C\" {\n"
327 	   "#endif\n");
328 
329   fprintf (out, "extern void GOMP_offload_register_ver"
330 	   " (unsigned, const void *, int, const void *);\n");
331   fprintf (out, "extern void GOMP_offload_unregister_ver"
332 	   " (unsigned, const void *, int, const void *);\n");
333 
334   fprintf (out, "#ifdef __cplusplus\n"
335 	   "}\n"
336 	   "#endif\n");
337 
338   fprintf (out, "extern const void *const __OFFLOAD_TABLE__[];\n\n");
339 
340   fprintf (out, "static __attribute__((constructor)) void init (void)\n"
341 	   "{\n"
342 	   "  GOMP_offload_register_ver (%#x, __OFFLOAD_TABLE__,"
343 	   " %d/*NVIDIA_PTX*/, &target_data);\n"
344 	   "};\n",
345 	   GOMP_VERSION_PACK (GOMP_VERSION, GOMP_VERSION_NVIDIA_PTX),
346 	   GOMP_DEVICE_NVIDIA_PTX);
347 
348   fprintf (out, "static __attribute__((destructor)) void fini (void)\n"
349 	   "{\n"
350 	   "  GOMP_offload_unregister_ver (%#x, __OFFLOAD_TABLE__,"
351 	   " %d/*NVIDIA_PTX*/, &target_data);\n"
352 	   "};\n",
353 	   GOMP_VERSION_PACK (GOMP_VERSION, GOMP_VERSION_NVIDIA_PTX),
354 	   GOMP_DEVICE_NVIDIA_PTX);
355 }
356 
357 static void
compile_native(const char * infile,const char * outfile,const char * compiler)358 compile_native (const char *infile, const char *outfile, const char *compiler)
359 {
360   const char *collect_gcc_options = getenv ("COLLECT_GCC_OPTIONS");
361   if (!collect_gcc_options)
362     fatal_error (input_location,
363 		 "environment variable COLLECT_GCC_OPTIONS must be set");
364 
365   struct obstack argv_obstack;
366   obstack_init (&argv_obstack);
367   obstack_ptr_grow (&argv_obstack, compiler);
368   if (save_temps)
369     obstack_ptr_grow (&argv_obstack, "-save-temps");
370   if (verbose)
371     obstack_ptr_grow (&argv_obstack, "-v");
372   switch (offload_abi)
373     {
374     case OFFLOAD_ABI_LP64:
375       obstack_ptr_grow (&argv_obstack, "-m64");
376       break;
377     case OFFLOAD_ABI_ILP32:
378       obstack_ptr_grow (&argv_obstack, "-m32");
379       break;
380     default:
381       gcc_unreachable ();
382     }
383   obstack_ptr_grow (&argv_obstack, infile);
384   obstack_ptr_grow (&argv_obstack, "-c");
385   obstack_ptr_grow (&argv_obstack, "-o");
386   obstack_ptr_grow (&argv_obstack, outfile);
387   obstack_ptr_grow (&argv_obstack, NULL);
388 
389   const char **new_argv = XOBFINISH (&argv_obstack, const char **);
390   fork_execute (new_argv[0], CONST_CAST (char **, new_argv), true);
391   obstack_free (&argv_obstack, NULL);
392 }
393 
394 int
main(int argc,char ** argv)395 main (int argc, char **argv)
396 {
397   FILE *in = stdin;
398   FILE *out = stdout;
399   const char *outname = 0;
400 
401   progname = "mkoffload";
402   diagnostic_initialize (global_dc, 0);
403 
404   if (atexit (mkoffload_cleanup) != 0)
405     fatal_error (input_location, "atexit failed");
406 
407   char *collect_gcc = getenv ("COLLECT_GCC");
408   if (collect_gcc == NULL)
409     fatal_error (input_location, "COLLECT_GCC must be set.");
410   const char *gcc_path = dirname (ASTRDUP (collect_gcc));
411   const char *gcc_exec = basename (ASTRDUP (collect_gcc));
412 
413   size_t len = (strlen (gcc_path) + 1
414 		+ strlen (GCC_INSTALL_NAME)
415 		+ 1);
416   char *driver = XALLOCAVEC (char, len);
417 
418   if (strcmp (gcc_exec, collect_gcc) == 0)
419     /* collect_gcc has no path, so it was found in PATH.  Make sure we also
420        find accel-gcc in PATH.  */
421     gcc_path = NULL;
422 
423   int driver_used = 0;
424   if (gcc_path != NULL)
425     driver_used = sprintf (driver, "%s/", gcc_path);
426   sprintf (driver + driver_used, "%s", GCC_INSTALL_NAME);
427 
428   bool found = false;
429   if (gcc_path == NULL)
430     found = true;
431   else if (access_check (driver, X_OK) == 0)
432     found = true;
433   else
434     {
435       /* Don't use alloca pointer with XRESIZEVEC.  */
436       driver = NULL;
437       /* Look in all COMPILER_PATHs for GCC_INSTALL_NAME.  */
438       char **paths = NULL;
439       unsigned n_paths;
440       n_paths = parse_env_var (getenv ("COMPILER_PATH"), &paths);
441       for (unsigned i = 0; i < n_paths; i++)
442 	{
443 	  len = strlen (paths[i]) + 1 + strlen (GCC_INSTALL_NAME) + 1;
444 	  driver = XRESIZEVEC (char, driver, len);
445 	  sprintf (driver, "%s/%s", paths[i], GCC_INSTALL_NAME);
446 	  if (access_check (driver, X_OK) == 0)
447 	    {
448 	      found = true;
449 	      break;
450 	    }
451 	}
452       free_array_of_ptrs ((void **) paths, n_paths);
453     }
454 
455   if (!found)
456     fatal_error (input_location,
457 		 "offload compiler %s not found (consider using %<-B%>)",
458 		 GCC_INSTALL_NAME);
459 
460   /* We may be called with all the arguments stored in some file and
461      passed with @file.  Expand them into argv before processing.  */
462   expandargv (&argc, &argv);
463 
464   /* Scan the argument vector.  */
465   bool fopenmp = false;
466   bool fopenacc = false;
467   for (int i = 1; i < argc; i++)
468     {
469 #define STR "-foffload-abi="
470       if (strncmp (argv[i], STR, strlen (STR)) == 0)
471 	{
472 	  if (strcmp (argv[i] + strlen (STR), "lp64") == 0)
473 	    offload_abi = OFFLOAD_ABI_LP64;
474 	  else if (strcmp (argv[i] + strlen (STR), "ilp32") == 0)
475 	    offload_abi = OFFLOAD_ABI_ILP32;
476 	  else
477 	    fatal_error (input_location,
478 			 "unrecognizable argument of option " STR);
479 	}
480 #undef STR
481       else if (strcmp (argv[i], "-fopenmp") == 0)
482 	fopenmp = true;
483       else if (strcmp (argv[i], "-fopenacc") == 0)
484 	fopenacc = true;
485       else if (strcmp (argv[i], "-save-temps") == 0)
486 	save_temps = true;
487       else if (strcmp (argv[i], "-v") == 0)
488 	verbose = true;
489     }
490   if (!(fopenacc ^ fopenmp))
491     fatal_error (input_location, "either %<-fopenacc%> or %<-fopenmp%> "
492 		 "must be set");
493 
494   struct obstack argv_obstack;
495   obstack_init (&argv_obstack);
496   obstack_ptr_grow (&argv_obstack, driver);
497   if (save_temps)
498     obstack_ptr_grow (&argv_obstack, "-save-temps");
499   if (verbose)
500     obstack_ptr_grow (&argv_obstack, "-v");
501   obstack_ptr_grow (&argv_obstack, "-xlto");
502   switch (offload_abi)
503     {
504     case OFFLOAD_ABI_LP64:
505       obstack_ptr_grow (&argv_obstack, "-m64");
506       break;
507     case OFFLOAD_ABI_ILP32:
508       obstack_ptr_grow (&argv_obstack, "-m32");
509       break;
510     default:
511       gcc_unreachable ();
512     }
513   if (fopenmp)
514     obstack_ptr_grow (&argv_obstack, "-mgomp");
515 
516   for (int ix = 1; ix != argc; ix++)
517     {
518       if (!strcmp (argv[ix], "-o") && ix + 1 != argc)
519 	outname = argv[++ix];
520       else
521 	obstack_ptr_grow (&argv_obstack, argv[ix]);
522     }
523 
524   ptx_cfile_name = make_temp_file (".c");
525 
526   out = fopen (ptx_cfile_name, "w");
527   if (!out)
528     fatal_error (input_location, "cannot open '%s'", ptx_cfile_name);
529 
530   /* PR libgomp/65099: Currently, we only support offloading in 64-bit
531      configurations.  */
532   if (offload_abi == OFFLOAD_ABI_LP64)
533     {
534       ptx_name = make_temp_file (".mkoffload");
535       obstack_ptr_grow (&argv_obstack, "-o");
536       obstack_ptr_grow (&argv_obstack, ptx_name);
537       obstack_ptr_grow (&argv_obstack, NULL);
538       const char **new_argv = XOBFINISH (&argv_obstack, const char **);
539 
540       char *execpath = getenv ("GCC_EXEC_PREFIX");
541       char *cpath = getenv ("COMPILER_PATH");
542       char *lpath = getenv ("LIBRARY_PATH");
543       unsetenv ("GCC_EXEC_PREFIX");
544       unsetenv ("COMPILER_PATH");
545       unsetenv ("LIBRARY_PATH");
546 
547       fork_execute (new_argv[0], CONST_CAST (char **, new_argv), true);
548       obstack_free (&argv_obstack, NULL);
549 
550       xputenv (concat ("GCC_EXEC_PREFIX=", execpath, NULL));
551       xputenv (concat ("COMPILER_PATH=", cpath, NULL));
552       xputenv (concat ("LIBRARY_PATH=", lpath, NULL));
553 
554       in = fopen (ptx_name, "r");
555       if (!in)
556 	fatal_error (input_location, "cannot open intermediate ptx file");
557 
558       process (in, out);
559     }
560 
561   fclose (out);
562 
563   compile_native (ptx_cfile_name, outname, collect_gcc);
564 
565   return 0;
566 }
567