diff options
author | Giuliano Belinassi <giuliano.belinassi@usp.br> | 2020-07-27 23:19:21 -0300 |
---|---|---|
committer | Giuliano Belinassi <giuliano.belinassi@usp.br> | 2020-07-27 23:19:21 -0300 |
commit | 47981062961083e03a08123169ed9d55ef59f633 (patch) | |
tree | b5235e255b50c47d244a5a8e021202a659bfeeae /gcc | |
parent | ef9d764a47f797420db9413de670d2e1e140afbc (diff) | |
download | gcc-47981062961083e03a08123169ed9d55ef59f633.zip gcc-47981062961083e03a08123169ed9d55ef59f633.tar.gz gcc-47981062961083e03a08123169ed9d55ef59f633.tar.bz2 |
Intergrate with GNU Jobserver
This commit makes GCC communicate with GNU Make's Jobserver using the
-fparallel-jobs=jobserver flag. It also disables automatic
parallelization when -fparallel-jobs are not providen.
gcc/ChangeLog
2020-07-27 Giuliano Belinassi <giuliano.belinassi@usp.br>
* Makefile.in: Mark jobserver.cc to be compiled.
* jobserver.cc: New file.
* cgraphunit.c (is_number): New function.
* (compile): Move parallelization logic to...
* (maybe_compile_in_parallel): Here.
* common.opt: New flag.
* gcc.c (execute): Check for fparallel-jobs before calling
append_split_outputs.
* toplev.c (main): Finalize jobserver before exit.
Diffstat (limited to 'gcc')
-rw-r--r-- | gcc/ChangeLog | 12 | ||||
-rw-r--r-- | gcc/Makefile.in | 1 | ||||
-rw-r--r-- | gcc/cgraphunit.c | 255 | ||||
-rw-r--r-- | gcc/common.opt | 8 | ||||
-rw-r--r-- | gcc/gcc.c | 2 | ||||
-rw-r--r-- | gcc/jobserver.cc | 185 | ||||
-rw-r--r-- | gcc/jobserver.h | 33 | ||||
-rw-r--r-- | gcc/toplev.c | 7 |
8 files changed, 406 insertions, 97 deletions
diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 80e57b0..ed48394 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,15 @@ +2020-07-27 Giuliano Belinassi <giuliano.belinassi@usp.br> + + * Makefile.in: Mark jobserver.cc to be compiled. + * jobserver.cc: New file. + * cgraphunit.c (is_number): New function. + * (compile): Move parallelization logic to... + * (maybe_compile_in_parallel): Here. + * common.opt: New flag. + * gcc.c (execute): Check for fparallel-jobs before calling + append_split_outputs. + * toplev.c (main): Finalize jobserver before exit. + 2020-07-16 Giuliano Belinassi <giuliano.belinassi@usp.br> * ipa-sra.c (gate): Enable when split_outputs. diff --git a/gcc/Makefile.in b/gcc/Makefile.in index a9b7926..ae2827e 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1429,6 +1429,7 @@ OBJS = \ ira-color.o \ ira-emit.o \ ira-lives.o \ + jobserver.o \ jump.o \ langhooks.o \ lcm.o \ diff --git a/gcc/cgraphunit.c b/gcc/cgraphunit.c index 6ea665a..a029455 100644 --- a/gcc/cgraphunit.c +++ b/gcc/cgraphunit.c @@ -207,6 +207,7 @@ along with GCC; see the file COPYING3. If not see #include "attribs.h" #include "ipa-inline.h" #include "lto-partition.h" +#include "jobserver.h" /* Queue of cgraph nodes scheduled to be added into cgraph. This is a secondary queue used during optimization to accommodate passes that @@ -2741,6 +2742,163 @@ symbol_table::output_weakrefs (void) } } +static bool is_number (const char *str) +{ + while (*str != '\0') + switch (*str++) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + continue; + default: + return false; + } + + return true; +} + +static bool maybe_compile_in_parallel (void) +{ + struct symtab_node *node; + int partitions, i, j; + int *pids; + + bool promote_statics = param_promote_statics; + bool balance = param_balance_partitions; + bool jobserver = false; + int num_jobs = -1; + + if (!flag_parallel_jobs || !split_outputs) + return false; + + if (!strcmp (flag_parallel_jobs, "jobserver")) + jobserver = jobserver_initialize (); + else if (is_number (flag_parallel_jobs)) + num_jobs = atoi (flag_parallel_jobs); + else + gcc_unreachable (); + + if (num_jobs < 0 && !jobserver) + { + error (UNKNOWN_LOCATION, + "'-fparallel-jobs=jobserver', but no GNU Jobserver found"); + return false; + } + + if (num_jobs == 0) + { + inform (UNKNOWN_LOCATION, "-fparallel-jobs=0 makes no sense"); + return false; + } + + /* Trick the compiler to think that we are in WPA. */ + flag_wpa = ""; + symtab_node::checking_verify_symtab_nodes (); + + /* Partition the program so that COMDATs get mapped to the same + partition. If promote_statics is true, it also maps statics + to the same partition. If balance is true, try to balance the + partitions for compilation performance. */ + lto_merge_comdat_map (balance, promote_statics); + + /* AUX pointers are used by partitioning code to bookkeep number of + partitions symbol is in. This is no longer needed. */ + FOR_EACH_SYMBOL (node) + node->aux = NULL; + + /* We decided that partitioning is a bad idea. In this case, just + proceed with the default compilation method. */ + if (ltrans_partitions.length () <= 1) + { + flag_wpa = NULL; + jobserver_finalize (); + return false; + } + + /* Find out statics that need to be promoted + to globals with hidden visibility because they are accessed from + multiple partitions. */ + lto_promote_cross_file_statics (promote_statics); + + /* Check if we have variables being referenced across partitions. */ + lto_check_usage_from_other_partitions (); + + /* Trick the compiler to think we are not in WPA anymore. */ + flag_wpa = NULL; + + partitions = ltrans_partitions.length (); + pids = XALLOCAVEC (pid_t, partitions); + + /* There is no point in launching more jobs than we have partitions. */ + if (num_jobs > partitions) + num_jobs = partitions; + + /* Trick the compiler to think we are in LTRANS mode. */ + flag_ltrans = true; + + init_additional_asm_names_file (); + + /* Flush asm file, so we don't get repeated output as we fork. */ + fflush (asm_out_file); + + /* Insert a token for child to consume. */ + if (jobserver) + { + num_jobs = partitions; + jobserver_return_token ('p'); + } + + /* Spawn processes. Spawn as soon as there is a free slot. */ + for (j = 0, i = -num_jobs; i < partitions; i++, j++) + { + if (i >= 0) + { + int wstatus, ret; + ret = waitpid (pids[i], &wstatus, 0); + + if (ret < 0) + internal_error ("Unable to wait child %d to finish", i); + else if (WIFEXITED (wstatus)) + { + if (WEXITSTATUS (wstatus) != 0) + error ("Child %d exited with error", i); + } + else if (WIFSIGNALED (wstatus)) + error ("Child %d aborted with error", i); + } + + if (j < partitions) + { + gcc_assert (ltrans_partitions[j]->symbols > 0); + + if (jobserver) + jobserver_get_token (); + + pids[j] = fork (); + if (pids[j] == 0) + { + lto_apply_partition_mask (ltrans_partitions[j]); + return true; + } + } + } + + /* Get the token which parent inserted for the childs, which they returned by + now. */ + if (jobserver) + jobserver_get_token (); + exit (0); +} + + /* Perform simple optimizations based on callgraph. */ void @@ -2767,102 +2925,7 @@ symbol_table::compile (const char *name) { timevar_start (TV_CGRAPH_IPA_PASSES); ipa_passes (); - - if (split_outputs) - { - struct symtab_node *node; - int partitions, i; - int *pids; - - bool promote_statics = param_promote_statics; - bool balance = param_balance_partitions; - - /* Trick the compiler to think that we are in WPA. */ - flag_wpa = ""; - symtab_node::checking_verify_symtab_nodes (); - - /* Partition the program so that COMDATs get mapped to the same - partition. If promote_statics is true, it also maps statics - to the same partition. If balance is true, try to balance the - partitions for compilation performance. */ - lto_merge_comdat_map (balance, promote_statics); - - /* AUX pointers are used by partitioning code to bookkeep number of - partitions symbol is in. This is no longer needed. */ - FOR_EACH_SYMBOL (node) - node->aux = NULL; - - /* We decided that partitioning is a bad idea. In this case, just - proceed with the default compilation method. */ - if (ltrans_partitions.length () <= 1) - { - flag_wpa = NULL; - goto continue_compilation; - } - - /* Find out statics that need to be promoted - to globals with hidden visibility because they are accessed from - multiple partitions. */ - lto_promote_cross_file_statics (promote_statics); - - /* Check if we have variables being referenced across partitions. */ - lto_check_usage_from_other_partitions (); - - /* Trick the compiler to think we are not in WPA anymore. */ - flag_wpa = NULL; - - partitions = ltrans_partitions.length (); - pids = (int *) alloca (partitions * sizeof (*pids)); - - /* Trick the compiler to think we are in LTRANS mode. */ - flag_ltrans = true; - - init_additional_asm_names_file (); - - /* Flush asm file, so we don't get repeated output as we fork. */ - fflush (asm_out_file); - - symtab_node::checking_verify_symtab_nodes (); - - /* Run serially for now. */ - for (i = 0; i < partitions; ++i) - { - gcc_assert (ltrans_partitions[i]->symbols > 0); - - pids[i] = fork (); - if (pids[i] == 0) - { - lto_apply_partition_mask (ltrans_partitions[i]); - - goto continue_compilation; - } - else - { - int wstatus; - waitpid (pids[i], &wstatus, 0); - - if (WIFEXITED (wstatus)) - { - if (WEXITSTATUS (wstatus) == 0) - continue; - else - { - fprintf (stderr, "Child %d exited with error\n", i); - internal_error ("Child exited with error"); - } - - } - else if (WIFSIGNALED (wstatus)) - { - fprintf (stderr, "Child %d aborted due to signal\n", i); - internal_error ("Child aborted with error"); - } - } - } - exit (0); - } - -continue_compilation: + maybe_compile_in_parallel (); timevar_stop (TV_CGRAPH_IPA_PASSES); } /* Do nothing else if any IPA pass found errors or if we are just streaming LTO. */ diff --git a/gcc/common.opt b/gcc/common.opt index 51ae35e..8b50b72 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -230,6 +230,14 @@ bool flag_disable_hsa = false Variable const char *split_outputs = NULL +; Set to compile in parallel with N jobs, or Jobserver. +Variable +const char *flag_parallel_jobs = NULL + +fparallel-jobs= +Common Driver RejectNegative Joined Var(flag_parallel_jobs) +Compiles in parallel with a number of parallel jobs, or jobserver. + ### Driver @@ -3877,7 +3877,7 @@ execute (void) /* Parse the argbuf into several commands. */ commands = parse_argbuf (&argbuf, &n_commands); - if (!have_S && !have_E) + if (!have_S && !have_E && flag_parallel_jobs) append_split_outputs (&storer, &additional_ld, &commands, &n_commands); if (!wrapper_string) diff --git a/gcc/jobserver.cc b/gcc/jobserver.cc new file mode 100644 index 0000000..4c879cc --- /dev/null +++ b/gcc/jobserver.cc @@ -0,0 +1,185 @@ +/* GNU Jobserver Integration Interface. + Copyright (C) 2005-2020 Free Software Foundation, Inc. + + Contributed by Giuliano Belinassi <giuliano.belinassi@usp.br> + +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 +<http://www.gnu.org/licenses/>. */ + +#include "jobserver.h" +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "diagnostic.h" +#include "errno.h" + +#define JOBSERVER_MAKE_TOKEN '+' /* Token which make sent when invoking GCC. */ + +bool jobserver_initialized = false; +bool nonblock_mode = false; +static int wfd = -1; +static int rfd = -1; + +jobserver_token_t jobserver_curr_token = JOBSERVER_NULL_TOKEN; + +/* When using GNU Jobserver but the user did not prepend the recursive make + token `+' to the GCC invocation, Make can close the file descriptors used + to comunicate with us, and there is no reliable way to detect this. + Therefore the best we can do is crash and alert the user to do hte right + thing. */ +static void jobserver_crash () +{ + error ("-fparallel-jobs=jobserver, but Make jobserver pipe is closed"); + inform (UNKNOWN_LOCATION, + "GCC must be invoked with a prepended `+' in the Makefile rule's command " + "to use the jobserver parallel mode"); + exit (1); +} + +/* Initialize this interface. We try to find whether the Jobserver is active + and working. */ +bool jobserver_initialize () +{ + bool success; + const char *makeflags = getenv ("MAKEFLAGS"); + if (makeflags == NULL) + return false; + + const char *needle = "--jobserver-auth="; + const char *n = strstr (makeflags, needle); + if (n == NULL) + return false; + + success = (sscanf (n + strlen (needle), "%d,%d", &rfd, &wfd) == 2 + && rfd > 0 + && wfd > 0 + && is_valid_fd (rfd) + && is_valid_fd (wfd)); + + if (!success) + return false; + + int flags = fcntl (rfd, F_GETFL); + if (flags < 0) + return false; + + /* Mark that rfd is O_NONBLOCK, as we will have to use select. */ + if (flags & O_NONBLOCK) + nonblock_mode = true; + + return (jobserver_initialized = true); + +#if 0 + /* Test if we can read from the rfd. */ + jobserver_token_t token; + ssize_t r = read (rfd, &token, sizeof (jobserver_token_t)); + + gcc_assert (r == sizeof (jobserver_token_t) || r == 0 || r == -1); + + if (r < 0) + { + /* Check errno. We may have no problem at all. */ + if (errno != EAGAIN) + return false; + } + + /* Reading 0 bytes indicate that fd was closed by parent process. */ + if (r == 0) + return false; + + /* Check if we got a important token that we have to return. */ + if (token != JOBSERVER_NULL_TOKEN) + jobserver_return_token (token); +#endif +} + +/* Finalize the jobserver, so this interface could be used again later. */ +bool jobserver_finalize () +{ + if (!jobserver_initialized) + return false; + + close (rfd); + close (wfd); + + rfd = wfd = -1; + + jobserver_initialized = false; + return true; +} + +/* Return token to the jobserver. If c is the NULL token, then return + the last token we got. */ +void jobserver_return_token (jobserver_token_t c) +{ + ssize_t w; + + if (c == JOBSERVER_NULL_TOKEN) + c = jobserver_curr_token; + + w = write (wfd, &c, sizeof (jobserver_token_t)); + + if (w <= 0) + jobserver_crash (); +} + +/* TODO: Check if select if available in our system. */ +#define HAVE_SELECT + +/* Retrieve a token from the Jobserver. We have two cases, in which we must be + careful. First is when the function pselect is available in our system, as + Make will set the read fd as nonblocking and will expect that we use select. + (see posixos.c in GNU Make sourcecode). + The other is when select is not available in our system, and Make will set + it as blocking. */ +char jobserver_get_token () +{ + jobserver_token_t ret = JOBSERVER_NULL_TOKEN; + ssize_t r = -1; + + while (r < 0) + { + if (nonblock_mode) + { +#ifdef HAVE_SELECT + fd_set readfd_set; + + FD_ZERO (&readfd_set); + FD_SET (rfd, &readfd_set); + + r = select (rfd+1, &readfd_set, NULL, NULL, NULL); + + if (r < 0 && errno == EAGAIN) + continue; + + gcc_assert (r > 0); +#else + internal_error ("Make set Jobserver pipe to nonblock mode, but select " + "is not supported in your system"); +#endif + } + + r = read (rfd, &ret, sizeof (jobserver_token_t)); + + if (!(r > 0 || (r < 0 && errno == EAGAIN))) + { + jobserver_crash (); + break; + } + } + + return (jobserver_curr_token = ret); +} diff --git a/gcc/jobserver.h b/gcc/jobserver.h new file mode 100644 index 0000000..2047cf4 --- /dev/null +++ b/gcc/jobserver.h @@ -0,0 +1,33 @@ +/* GNU Jobserver Integration Interface. + Copyright (C) 2005-2020 Free Software Foundation, Inc. + + Contributed by Giuliano Belinassi <giuliano.belinassi@usp.br> + +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 +<http://www.gnu.org/licenses/>. */ + +#define JOBSERVER_NULL_TOKEN ('\0') + +typedef char jobserver_token_t; + +extern bool jobserver_initialized; +extern jobserver_token_t jobserver_curr_token; + +bool jobserver_initialize (); +bool jobserver_finalize (); +jobserver_token_t jobserver_get_token (); +void jobserver_return_token (jobserver_token_t); + diff --git a/gcc/toplev.c b/gcc/toplev.c index b62ba25..057812d 100644 --- a/gcc/toplev.c +++ b/gcc/toplev.c @@ -85,6 +85,7 @@ along with GCC; see the file COPYING3. If not see #include "dump-context.h" #include "print-tree.h" #include "optinfo-emit-json.h" +#include "jobserver.h" #if defined(DBX_DEBUGGING_INFO) || defined(XCOFF_DEBUGGING_INFO) #include "dbxout.h" @@ -2481,6 +2482,12 @@ toplev::main (int argc, char **argv) finalize_plugins (); + if (jobserver_initialized) + { + jobserver_return_token (JOBSERVER_NULL_TOKEN); + jobserver_finalize (); + } + after_memory_report = true; if (seen_error () || werrorcount) |