1/* Offload image generation tool for Intel MIC devices.
2
3   Copyright (C) 2014-2015 Free Software Foundation, Inc.
4
5   Contributed by Ilya Verbin <ilya.verbin@intel.com>.
6
7   This file is part of GCC.
8
9   GCC is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; either version 3, or (at your option)
12   any later version.
13
14   GCC is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19   You should have received a copy of the GNU General Public License
20   along with GCC; see the file COPYING3.  If not see
21   <http://www.gnu.org/licenses/>.  */
22
23#include "config.h"
24#include <libgen.h>
25#include "system.h"
26#include "coretypes.h"
27#include "obstack.h"
28#include "intl.h"
29#include "diagnostic.h"
30#include "collect-utils.h"
31#include "intelmic-offload.h"
32
33const char tool_name[] = "intelmic mkoffload";
34
35const char image_section_name[] = ".gnu.offload_images";
36const char *symbols[3] = { "__offload_image_intelmic_start",
37			   "__offload_image_intelmic_end",
38			   "__offload_image_intelmic_size" };
39const char *out_obj_filename = NULL;
40
41int num_temps = 0;
42const int MAX_NUM_TEMPS = 10;
43const char *temp_files[MAX_NUM_TEMPS];
44
45/* Shows if we should compile binaries for i386 instead of x86-64.  */
46bool target_ilp32 = false;
47
48/* Delete tempfiles and exit function.  */
49void
50tool_cleanup (bool from_signal ATTRIBUTE_UNUSED)
51{
52  for (int i = 0; i < num_temps; i++)
53    maybe_unlink (temp_files[i]);
54}
55
56static void
57mkoffload_atexit (void)
58{
59  tool_cleanup (false);
60}
61
62/* Unlink FILE unless we are debugging.  */
63void
64maybe_unlink (const char *file)
65{
66  if (debug)
67    notice ("[Leaving %s]\n", file);
68  else
69    unlink_if_ordinary (file);
70}
71
72/* Add or change the value of an environment variable, outputting the
73   change to standard error if in verbose mode.  */
74static void
75xputenv (const char *string)
76{
77  if (verbose)
78    fprintf (stderr, "%s\n", string);
79  putenv (CONST_CAST (char *, string));
80}
81
82/* Parse STR, saving found tokens into PVALUES and return their number.
83   Tokens are assumed to be delimited by ':'.  */
84static unsigned
85parse_env_var (const char *str, char ***pvalues)
86{
87  const char *curval, *nextval;
88  char **values;
89  unsigned num = 1, i;
90
91  curval = strchr (str, ':');
92  while (curval)
93    {
94      num++;
95      curval = strchr (curval + 1, ':');
96    }
97
98  values = (char **) xmalloc (num * sizeof (char *));
99  curval = str;
100  nextval = strchr (curval, ':');
101  if (nextval == NULL)
102    nextval = strchr (curval, '\0');
103
104  for (i = 0; i < num; i++)
105    {
106      int l = nextval - curval;
107      values[i] = (char *) xmalloc (l + 1);
108      memcpy (values[i], curval, l);
109      values[i][l] = 0;
110      curval = nextval + 1;
111      nextval = strchr (curval, ':');
112      if (nextval == NULL)
113	nextval = strchr (curval, '\0');
114    }
115  *pvalues = values;
116  return num;
117}
118
119/* Auxiliary function that frees elements of PTR and PTR itself.
120   N is number of elements to be freed.  If PTR is NULL, nothing is freed.
121   If an element is NULL, subsequent elements are not freed.  */
122static void
123free_array_of_ptrs (void **ptr, unsigned n)
124{
125  unsigned i;
126  if (!ptr)
127    return;
128  for (i = 0; i < n; i++)
129    {
130      if (!ptr[i])
131	break;
132      free (ptr[i]);
133    }
134  free (ptr);
135  return;
136}
137
138/* Check whether NAME can be accessed in MODE.  This is like access,
139   except that it never considers directories to be executable.  */
140static int
141access_check (const char *name, int mode)
142{
143  if (mode == X_OK)
144    {
145      struct stat st;
146
147      if (stat (name, &st) < 0 || S_ISDIR (st.st_mode))
148	return -1;
149    }
150
151  return access (name, mode);
152}
153
154/* Find target compiler using a path from COLLECT_GCC or COMPILER_PATH.  */
155static char *
156find_target_compiler (const char *name)
157{
158  bool found = false;
159  char **paths = NULL;
160  unsigned n_paths, i;
161  char *target_compiler;
162  const char *collect_gcc = getenv ("COLLECT_GCC");
163  const char *gcc_path = dirname (ASTRDUP (collect_gcc));
164  const char *gcc_exec = basename (ASTRDUP (collect_gcc));
165
166  if (strcmp (gcc_exec, collect_gcc) == 0)
167    {
168      /* collect_gcc has no path, so it was found in PATH.  Make sure we also
169	 find accel-gcc in PATH.  */
170      target_compiler = XDUPVEC (char, name, strlen (name) + 1);
171      found = true;
172      goto out;
173    }
174
175  target_compiler = concat (gcc_path, "/", name, NULL);
176  if (access_check (target_compiler, X_OK) == 0)
177    {
178      found = true;
179      goto out;
180    }
181
182  n_paths = parse_env_var (getenv ("COMPILER_PATH"), &paths);
183  for (i = 0; i < n_paths; i++)
184    {
185      size_t len = strlen (paths[i]) + 1 + strlen (name) + 1;
186      target_compiler = XRESIZEVEC (char, target_compiler, len);
187      sprintf (target_compiler, "%s/%s", paths[i], name);
188      if (access_check (target_compiler, X_OK) == 0)
189	{
190	  found = true;
191	  break;
192	}
193    }
194
195out:
196  free_array_of_ptrs ((void **) paths, n_paths);
197  return found ? target_compiler : NULL;
198}
199
200static void
201compile_for_target (struct obstack *argv_obstack)
202{
203  if (target_ilp32)
204    obstack_ptr_grow (argv_obstack, "-m32");
205  else
206    obstack_ptr_grow (argv_obstack, "-m64");
207  obstack_ptr_grow (argv_obstack, NULL);
208  char **argv = XOBFINISH (argv_obstack, char **);
209
210  /* Save environment variables.  */
211  const char *epath = getenv ("GCC_EXEC_PREFIX");
212  const char *cpath = getenv ("COMPILER_PATH");
213  const char *lpath = getenv ("LIBRARY_PATH");
214  const char *rpath = getenv ("LD_RUN_PATH");
215  unsetenv ("GCC_EXEC_PREFIX");
216  unsetenv ("COMPILER_PATH");
217  unsetenv ("LIBRARY_PATH");
218  unsetenv ("LD_RUN_PATH");
219
220  fork_execute (argv[0], argv, false);
221  obstack_free (argv_obstack, NULL);
222
223  /* Restore environment variables.  */
224  xputenv (concat ("GCC_EXEC_PREFIX=", epath, NULL));
225  xputenv (concat ("COMPILER_PATH=", cpath, NULL));
226  xputenv (concat ("LIBRARY_PATH=", lpath, NULL));
227  xputenv (concat ("LD_RUN_PATH=", rpath, NULL));
228}
229
230/* Generates object file with the descriptor for the target library.  */
231static const char *
232generate_target_descr_file (const char *target_compiler)
233{
234  const char *src_filename = make_temp_file ("_target_descr.c");
235  const char *obj_filename = make_temp_file ("_target_descr.o");
236  temp_files[num_temps++] = src_filename;
237  temp_files[num_temps++] = obj_filename;
238  FILE *src_file = fopen (src_filename, "w");
239
240  if (!src_file)
241    fatal_error (input_location, "cannot open '%s'", src_filename);
242
243  fprintf (src_file,
244	   "extern void *__offload_funcs_end[];\n"
245	   "extern void *__offload_vars_end[];\n\n"
246
247	   "void *__offload_func_table[0]\n"
248	   "__attribute__ ((__used__, visibility (\"hidden\"),\n"
249	   "section (\".gnu.offload_funcs\"))) = { };\n\n"
250
251	   "void *__offload_var_table[0]\n"
252	   "__attribute__ ((__used__, visibility (\"hidden\"),\n"
253	   "section (\".gnu.offload_vars\"))) = { };\n\n"
254
255	   "void *__OFFLOAD_TARGET_TABLE__[]\n"
256	   "__attribute__ ((__used__, visibility (\"hidden\"))) = {\n"
257	   "  &__offload_func_table, &__offload_funcs_end,\n"
258	   "  &__offload_var_table, &__offload_vars_end\n"
259	   "};\n\n");
260
261  fprintf (src_file,
262	   "#ifdef __cplusplus\n"
263	   "extern \"C\"\n"
264	   "#endif\n"
265	   "void target_register_lib (const void *);\n\n"
266
267	   "__attribute__((constructor))\n"
268	   "static void\n"
269	   "init (void)\n"
270	   "{\n"
271	   "  target_register_lib (__OFFLOAD_TARGET_TABLE__);\n"
272	   "}\n");
273  fclose (src_file);
274
275  struct obstack argv_obstack;
276  obstack_init (&argv_obstack);
277  obstack_ptr_grow (&argv_obstack, target_compiler);
278  obstack_ptr_grow (&argv_obstack, "-c");
279  obstack_ptr_grow (&argv_obstack, "-shared");
280  obstack_ptr_grow (&argv_obstack, "-fPIC");
281  obstack_ptr_grow (&argv_obstack, src_filename);
282  obstack_ptr_grow (&argv_obstack, "-o");
283  obstack_ptr_grow (&argv_obstack, obj_filename);
284  compile_for_target (&argv_obstack);
285
286  return obj_filename;
287}
288
289/* Generates object file with __offload_*_end symbols for the target
290   library.  */
291static const char *
292generate_target_offloadend_file (const char *target_compiler)
293{
294  const char *src_filename = make_temp_file ("_target_offloadend.c");
295  const char *obj_filename = make_temp_file ("_target_offloadend.o");
296  temp_files[num_temps++] = src_filename;
297  temp_files[num_temps++] = obj_filename;
298  FILE *src_file = fopen (src_filename, "w");
299
300  if (!src_file)
301    fatal_error (input_location, "cannot open '%s'", src_filename);
302
303  fprintf (src_file,
304	   "void *__offload_funcs_end[0]\n"
305	   "__attribute__ ((__used__, visibility (\"hidden\"),\n"
306	   "section (\".gnu.offload_funcs\"))) = { };\n\n"
307
308	   "void *__offload_vars_end[0]\n"
309	   "__attribute__ ((__used__, visibility (\"hidden\"),\n"
310	   "section (\".gnu.offload_vars\"))) = { };\n");
311  fclose (src_file);
312
313  struct obstack argv_obstack;
314  obstack_init (&argv_obstack);
315  obstack_ptr_grow (&argv_obstack, target_compiler);
316  obstack_ptr_grow (&argv_obstack, "-c");
317  obstack_ptr_grow (&argv_obstack, "-shared");
318  obstack_ptr_grow (&argv_obstack, "-fPIC");
319  obstack_ptr_grow (&argv_obstack, src_filename);
320  obstack_ptr_grow (&argv_obstack, "-o");
321  obstack_ptr_grow (&argv_obstack, obj_filename);
322  compile_for_target (&argv_obstack);
323
324  return obj_filename;
325}
326
327/* Generates object file with the host side descriptor.  */
328static const char *
329generate_host_descr_file (const char *host_compiler)
330{
331  const char *src_filename = make_temp_file ("_host_descr.c");
332  const char *obj_filename = make_temp_file ("_host_descr.o");
333  temp_files[num_temps++] = src_filename;
334  temp_files[num_temps++] = obj_filename;
335  FILE *src_file = fopen (src_filename, "w");
336
337  if (!src_file)
338    fatal_error (input_location, "cannot open '%s'", src_filename);
339
340  fprintf (src_file,
341	   "extern void *__OFFLOAD_TABLE__;\n"
342	   "extern void *__offload_image_intelmic_start;\n"
343	   "extern void *__offload_image_intelmic_end;\n\n"
344
345	   "static const void *__offload_target_data[] = {\n"
346	   "  &__offload_image_intelmic_start, &__offload_image_intelmic_end\n"
347	   "};\n\n");
348
349  fprintf (src_file,
350	   "#ifdef __cplusplus\n"
351	   "extern \"C\"\n"
352	   "#endif\n"
353	   "void GOMP_offload_register (void *, int, void *);\n"
354	   "#ifdef __cplusplus\n"
355	   "extern \"C\"\n"
356	   "#endif\n"
357	   "void GOMP_offload_unregister (void *, int, void *);\n\n"
358
359	   "__attribute__((constructor))\n"
360	   "static void\n"
361	   "init (void)\n"
362	   "{\n"
363	   "  GOMP_offload_register (&__OFFLOAD_TABLE__, %d, __offload_target_data);\n"
364	   "}\n\n", GOMP_DEVICE_INTEL_MIC);
365
366  fprintf (src_file,
367	   "__attribute__((destructor))\n"
368	   "static void\n"
369	   "fini (void)\n"
370	   "{\n"
371	   "  GOMP_offload_unregister (&__OFFLOAD_TABLE__, %d, __offload_target_data);\n"
372	   "}\n", GOMP_DEVICE_INTEL_MIC);
373
374  fclose (src_file);
375
376  unsigned new_argc = 0;
377  const char *new_argv[9];
378  new_argv[new_argc++] = host_compiler;
379  new_argv[new_argc++] = "-c";
380  new_argv[new_argc++] = "-fPIC";
381  new_argv[new_argc++] = "-shared";
382  if (target_ilp32)
383    new_argv[new_argc++] = "-m32";
384  else
385    new_argv[new_argc++] = "-m64";
386  new_argv[new_argc++] = src_filename;
387  new_argv[new_argc++] = "-o";
388  new_argv[new_argc++] = obj_filename;
389  new_argv[new_argc++] = NULL;
390
391  fork_execute (new_argv[0], CONST_CAST (char **, new_argv), false);
392
393  return obj_filename;
394}
395
396static const char *
397prepare_target_image (const char *target_compiler, int argc, char **argv)
398{
399  const char *target_descr_filename
400    = generate_target_descr_file (target_compiler);
401  const char *target_offloadend_filename
402    = generate_target_offloadend_file (target_compiler);
403
404  char *opt1
405    = XALLOCAVEC (char, sizeof ("-Wl,") + strlen (target_descr_filename));
406  char *opt2
407    = XALLOCAVEC (char, sizeof ("-Wl,") + strlen (target_offloadend_filename));
408  sprintf (opt1, "-Wl,%s", target_descr_filename);
409  sprintf (opt2, "-Wl,%s", target_offloadend_filename);
410
411  const char *target_so_filename = make_temp_file ("_offload_intelmic.so");
412  temp_files[num_temps++] = target_so_filename;
413  struct obstack argv_obstack;
414  obstack_init (&argv_obstack);
415  obstack_ptr_grow (&argv_obstack, target_compiler);
416  obstack_ptr_grow (&argv_obstack, "-xlto");
417  obstack_ptr_grow (&argv_obstack, "-shared");
418  obstack_ptr_grow (&argv_obstack, "-fPIC");
419  obstack_ptr_grow (&argv_obstack, opt1);
420  for (int i = 1; i < argc; i++)
421    {
422      if (!strcmp (argv[i], "-o") && i + 1 != argc)
423	out_obj_filename = argv[++i];
424      else
425	obstack_ptr_grow (&argv_obstack, argv[i]);
426    }
427  if (!out_obj_filename)
428    fatal_error (input_location, "output file not specified");
429  obstack_ptr_grow (&argv_obstack, opt2);
430  obstack_ptr_grow (&argv_obstack, "-o");
431  obstack_ptr_grow (&argv_obstack, target_so_filename);
432  compile_for_target (&argv_obstack);
433
434  /* Run objcopy.  */
435  char *rename_section_opt
436    = XALLOCAVEC (char, sizeof (".data=") + strlen (image_section_name));
437  sprintf (rename_section_opt, ".data=%s", image_section_name);
438  const char *objcopy_argv[11];
439  objcopy_argv[0] = "objcopy";
440  objcopy_argv[1] = "-B";
441  objcopy_argv[2] = "i386";
442  objcopy_argv[3] = "-I";
443  objcopy_argv[4] = "binary";
444  objcopy_argv[5] = "-O";
445  if (target_ilp32)
446    objcopy_argv[6] = "elf32-i386";
447  else
448    objcopy_argv[6] = "elf64-x86-64";
449  objcopy_argv[7] = target_so_filename;
450  objcopy_argv[8] = "--rename-section";
451  objcopy_argv[9] = rename_section_opt;
452  objcopy_argv[10] = NULL;
453  fork_execute (objcopy_argv[0], CONST_CAST (char **, objcopy_argv), false);
454
455  /* Objcopy has created symbols, containing the input file name with
456     non-alphanumeric characters replaced by underscores.
457     We are going to rename these new symbols.  */
458  size_t symbol_name_len = strlen (target_so_filename);
459  char *symbol_name = XALLOCAVEC (char, symbol_name_len + 1);
460  for (size_t i = 0; i < symbol_name_len; i++)
461    {
462      char c = target_so_filename[i];
463      if (!ISALNUM (c))
464	c = '_';
465      symbol_name[i] = c;
466    }
467  symbol_name[symbol_name_len] = '\0';
468
469  char *opt_for_objcopy[3];
470  opt_for_objcopy[0] = XALLOCAVEC (char, sizeof ("_binary__start=")
471					 + symbol_name_len
472					 + strlen (symbols[0]));
473  opt_for_objcopy[1] = XALLOCAVEC (char, sizeof ("_binary__end=")
474					 + symbol_name_len
475					 + strlen (symbols[1]));
476  opt_for_objcopy[2] = XALLOCAVEC (char, sizeof ("_binary__size=")
477					 + symbol_name_len
478					 + strlen (symbols[2]));
479  sprintf (opt_for_objcopy[0], "_binary_%s_start=%s", symbol_name, symbols[0]);
480  sprintf (opt_for_objcopy[1], "_binary_%s_end=%s", symbol_name, symbols[1]);
481  sprintf (opt_for_objcopy[2], "_binary_%s_size=%s", symbol_name, symbols[2]);
482
483  objcopy_argv[0] = "objcopy";
484  objcopy_argv[1] = target_so_filename;
485  objcopy_argv[2] = "--redefine-sym";
486  objcopy_argv[3] = opt_for_objcopy[0];
487  objcopy_argv[4] = "--redefine-sym";
488  objcopy_argv[5] = opt_for_objcopy[1];
489  objcopy_argv[6] = "--redefine-sym";
490  objcopy_argv[7] = opt_for_objcopy[2];
491  objcopy_argv[8] = NULL;
492  fork_execute (objcopy_argv[0], CONST_CAST (char **, objcopy_argv), false);
493
494  return target_so_filename;
495}
496
497int
498main (int argc, char **argv)
499{
500  progname = "mkoffload-intelmic";
501  gcc_init_libintl ();
502  diagnostic_initialize (global_dc, 0);
503
504  if (atexit (mkoffload_atexit) != 0)
505    fatal_error (input_location, "atexit failed");
506
507  const char *host_compiler = getenv ("COLLECT_GCC");
508  if (!host_compiler)
509    fatal_error (input_location, "COLLECT_GCC must be set");
510
511  const char *target_driver_name = GCC_INSTALL_NAME;
512  char *target_compiler = find_target_compiler (target_driver_name);
513  if (target_compiler == NULL)
514    fatal_error (input_location, "offload compiler %s not found",
515		 target_driver_name);
516
517  /* We may be called with all the arguments stored in some file and
518     passed with @file.  Expand them into argv before processing.  */
519  expandargv (&argc, &argv);
520
521  /* Find out whether we should compile binaries for i386 or x86-64.  */
522  for (int i = argc - 1; i > 0; i--)
523    if (strncmp (argv[i], "-foffload-abi=", sizeof ("-foffload-abi=") - 1) == 0)
524      {
525	if (strstr (argv[i], "ilp32"))
526	  target_ilp32 = true;
527	else if (!strstr (argv[i], "lp64"))
528	  fatal_error (input_location,
529		       "unrecognizable argument of option -foffload-abi");
530	break;
531      }
532
533  const char *target_so_filename
534    = prepare_target_image (target_compiler, argc, argv);
535
536  const char *host_descr_filename = generate_host_descr_file (host_compiler);
537
538  /* Perform partial linking for the target image and host side descriptor.
539     As a result we'll get a finalized object file with all offload data.  */
540  unsigned new_argc = 0;
541  const char *new_argv[9];
542  new_argv[new_argc++] = "ld";
543  new_argv[new_argc++] = "-m";
544  if (target_ilp32)
545    new_argv[new_argc++] = "elf_i386";
546  else
547    new_argv[new_argc++] = "elf_x86_64";
548  new_argv[new_argc++] = "--relocatable";
549  new_argv[new_argc++] = host_descr_filename;
550  new_argv[new_argc++] = target_so_filename;
551  new_argv[new_argc++] = "-o";
552  new_argv[new_argc++] = out_obj_filename;
553  new_argv[new_argc++] = NULL;
554  fork_execute (new_argv[0], CONST_CAST (char **, new_argv), false);
555
556  /* Run objcopy on the resultant object file to localize generated symbols
557     to avoid conflicting between different DSO and an executable.  */
558  new_argv[0] = "objcopy";
559  new_argv[1] = "-L";
560  new_argv[2] = symbols[0];
561  new_argv[3] = "-L";
562  new_argv[4] = symbols[1];
563  new_argv[5] = "-L";
564  new_argv[6] = symbols[2];
565  new_argv[7] = out_obj_filename;
566  new_argv[8] = NULL;
567  fork_execute (new_argv[0], CONST_CAST (char **, new_argv), false);
568
569  return 0;
570}
571